Why is in this case fork way better than threads? With a file of 139 mb (rockyou.txt) fork is 0.5 seconds, with the same file and the same word at the end of the file it's 3 second with thread (measured with both clock () and normal stopwatch, threads take much longer than the fork)
The program reads each line of a wordlist, hash it and compare with a digest.
This one is with fork
void wordlistFork(char digest[], char hashtype[], FILE *wordlist,int numberOfFork){
int i;
clock_t t;
fseek(wordlist, 0L, SEEK_END);
long fileLength = ftell(wordlist);
fseek(wordlist, 0L, SEEK_SET);
for(i=0;i<numberOfFork;i++){
int pid = fork();
if(pid==0){
char line[512];
long initialOffset = correctOffset(lengthOfFile*i/numberOfFork,wordlist);
long finalOffset = correctOffset(lengthOfFile*(i+1)/numberOfFork,wordlist);
fseek(wordlist, initialOffset, SEEK_SET);
t = clock();
while (initialOffset < finalOffset) {
fscanf(wordlist,"%s\n",line);
char tempLine[512] = {0};
strncpy(tempLine,line,strlen(line));
if (strcmp(hash(tempLine,hashType), digest) == 0) {
printf("Child %d: Trovato! hash %s %s -> %s\n",i, tipohash, digest, linea);
t = clock() -t;
double time_taken = ((double)t)/CLOCKS_PER_SEC;
printf("Time elapsed: %f seconds\n",time_taken);
system("killall hashcrack");
return;
}
initialOffset = ftell(wordlist);
}
printf("Child %d Found nothing.\n",i);
exit(0);
}
}
for(i=0;i<numberOfFork;i++)
wait(NULL);
}
This one is with threads
for(i=0;i<numberOfThreads;i++)
pthread_create(&threads[i], NULL, (void *(*)(void *)) crack, (void *)(intptr_t) i);
for(i=0;i<numberOfThreads;i++)
pthread_join(threads[i],NULL);
void *crack(const int *args){
int threadID = (int)(intptr_t)args;
char line[512];
FILE *wordlist = fopen(Tpath,"r");
long initialOffset = correctOffset(fileLength*threadID/numberOfThreads,wordlist);
long finalOffset = correctOffset(fileLength*(threadID+1)/numberOfThreads,wordlist);
fseek(wordlist, initialOffset, SEEK_SET);
while (initialOffset < FinalOffset) {
fscanf(wordlist,"%s\n",line);
char tempLine[512] = {0};
strncpy(tempLine,line,strlen(line));
if (strcmp(hash(tempLine,hashType), Tdigest) == 0) {
printf("Thread %d: Found! hash %s %s -> %s\n",threadID, hashType, Tdigest, line);
system("killall hashcrack");
exit(0);
}
initialOffset = ftell(wordlist);
}
printf("Thread %d: found nothing.\n",threadID);
return NULL;
}
Your code's performance is indeterminate and based on the order in which work happens to get done. It probably just so happens that in one case the end of the file gets tested first and in the other case the beginning gets tested first. Your code doesn't enforce any particular order, so it's heavily dependent on quirks of scheduling order.
It's like if you test searching identical gardens, using two different algorithms, for a hidden egg. If the egg is in the same place each time, an algorithm that just happens to search where the egg is earlier in its search pattern will always win, even if it's not a better algorithm.
Related
SHORT: My problem is that the following function which sum the content of a given array in a given range, doesn't take the same execution time for the same task as the parent if called from a child. And by same I mean similar values. Because after some tests, the differences is for about a ~40% more for the child to execute.
LONG: I'm studying computer science and I'm working on the final project of a course. The problem is to sum all the cells of an int array by n processes distributing the workload. then confront it to a single calculation made by parent. The point is to demonstrate that, with big arrays, multiple process can reduce the execution time.
However, the sum of all children's times is always more than the parent's even with 50milions data.
Following just the function and the struct I use to pass results.
typedef struct cResults{
double time;
int result;
}cResult;
cResult arraySum(int start, int length, int* dataset)
{
/*Time unit to calculate the computation time*/
clock_t tic, toc = 0;
cResult chunk = {.result = 0, .time =0};
tic = clock(); // Start measure time
for (int i = start; i < start + length; i++)
{
chunk.result += dataset[i];
}
toc = clock(); // Stop measure time
chunk.time = ((double) (toc - tic))/ CLOCKS_PER_SEC;
printf("B: start at: %d, length: %d, sum: %d, in: %f\n", start, length, chunk.result, chunk.time); //! DEBUG
return chunk;
}
WHAT I'VE TRIED SO FAR:
Since the array is dynamically allocated, I've thought that it could be a bottleneck on the memory access. However, this question (Malloc returns same address in parent and child process after fork) lifted all doubt that even if heap allocated, they are not the same, but a copy of the array.
I've double checked that the parent will sum correctly and only once the elapsed time communicated by all the children, and then added the print() statement just to read and sum manually on the terminal all the results. And again, all checks.
I've tried moving the function call by parent from before to after all children were done, but no changes, then I've tried make the parent sleep() right after fork() (this was counterproductive for the purpose of the project but just to make sure) for avoiding resource queue.
The random numbers in the array are produced in a repeatable way through a seed, so I've tried same datasets that of course will give almost identical outputs, and again times will change slightly yet maintaining the single execution faster.
Ultimately I've tried to fork a single child and make it calculate the same range as the parent (so all the array). The time is on average 45% slower on children.
Surely I'm missing a simple thing but i run out of ideas... please be patient I'm learning by my self at my best.
UPDATE 1:
Since I've been asked for a full program, I've refactored mine. However, whereas the project required a single source file I've removed all of non regarding parts of our issue, hence it should be lighter. Unfortunately the frame you'll see that handle the pipe communications is a bit complex I'm afraid but it serve for other purpose that has been removed yet essential to make this works. I do hope it won't be a problem.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/* ----- PROTOTYPES ----- */
#define MIN 5 // Least number of process and threads
#define MAX 10 // Maximum number of process and threads
#define MSGSIZE 4 // Size of pipe messages
/* =================
*PIPE MESSAGES*
sum = Sum from the next given start to the next given roof.
end = End process
tmr = Return the computation time measured by the process
================= */
/// #brief Struct containing proces id (pid), the result and it's pipe file descriptors
struct process{
pid_t pid;
long int result;
bool done;
double time;
int fd[2];
};
/// #brief Struct that containts the result of the computation
typedef struct cResults{
double time;
long int result;
}cResult;
/// #brief W = write, R = read
enum PIPECONTROLLER{
R = 0,
W = 1
};
/* ----- PROTOTYPES ----- */
cResult arraySum(int start, int length, int* dataset);
int main()
{
/* =================
*USER DEFINED*
================= */
int dataLength = 50000000; // Set the length of the dataset
int nProcess = 1; // How many child process do you want
unsigned int seed = 1; // Change the randomization seed of the data
// System
int* data;
cResult chunk; // Used for storing temporary computational values
// Task related
int taskLenght;
int remainder;
// Pipe related
char pipeMSG[MSGSIZE];
int msgCheck = 1;
/// #brief Processes dashboard
struct process processes[MAX + 1] = { {.pid = 0, .result = 0, .done = false, .time = 0} };
data = malloc(sizeof(int) * dataLength);
srand(seed);
for (int i = 0; i < dataLength; i++)
{
/*Random population between 0-100*/
data[i] = rand() % 100;
}
chunk = arraySum(0, dataLength, data);
processes[nProcess + 1].result = chunk.result;
processes[nProcess + 1].time = chunk.time;
printf("\nCHECK SUM: %ld\n", chunk.result);// ! Debug
#pragma region "START PROCESSES"
/*Calculate how to separate the overhead for the processes*/
taskLenght = dataLength / nProcess;
remainder = dataLength % nProcess;
pid_t myPid = 0;
int startPoint = 0;
processes[nProcess + 1 ].pid = getpid();
/*Open child to parent pipe*/
if (pipe(processes[nProcess + 1 ].fd) == -1)
{
printf("Failed to open pipe on parent\n");
return 1;
}
for (int i = 0; i < nProcess; i++)
{
/*Open new parent to child pipe*/
if (pipe(processes[i].fd) == -1)
{
printf("Failed to open pipe on parent\n");
return 1;
}
myPid = fork();
switch (myPid)
{
case -1: // Error on fork
printf("An error occured while forking the %d process.\n", i);
return 1;
break;
case 0: // Child case
/*Record pid in the dashboard*/
processes[i].pid = getpid();
/*Handle the pipes descriptors*/
close(processes[i].fd[W]);
close(processes[nProcess + 1 ].fd[R]);
i = nProcess;
break;
default: // Parent case
/* Record the pid process into the dashrboard and increment the starting for the next*/
processes[i].pid = myPid;
startPoint += taskLenght;
/*Handle the pipes descriptors*/
close(processes[i].fd[R]);
break;
}
}
/*=========== CHILD PROCESS HANDLER ===========*/
if(myPid == 0)
{
int myID;
bool keepAlive = true;
for(myID = 0; myID < nProcess; myID++)
{
if (processes[myID].pid == getpid())
{
break;
}
}
/*Calculate first iteration of the sum*/
cResult temp = arraySum(startPoint, taskLenght, data);
chunk.result = temp.result;
chunk.time = temp.time;
while(keepAlive)
{
/*Comunicate the id of the process*/
if (write(processes[nProcess + 1 ].fd[W], &myID, sizeof(int)) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
/*Communicate the result of the operation*/
if (write(processes[nProcess + 1 ].fd[W], &chunk.result, sizeof(int)) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
/*Communicate the time elapsed for the operation*/
if (write(processes[nProcess + 1 ].fd[W], &chunk.time, sizeof(double)) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
/*Waits for further instruction*/
msgCheck = read(processes[myID].fd[R], pipeMSG, MSGSIZE);
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
/*Sum command*/
if(!strcmp(pipeMSG, "sum"))
{
msgCheck = read(processes[myID].fd[R], &startPoint, sizeof(int));
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
msgCheck = read(processes[myID].fd[R], &taskLenght, sizeof(int));
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
/*Calculate second iteration for the remaining part*/
temp = arraySum(startPoint, taskLenght, data);
chunk.result += temp.result;
chunk.time += temp.time;
}
/*Close command*/
if(!strcmp(pipeMSG, "end"))
{
keepAlive = false;
}
}
free(data);
close(processes[myID].fd[R]);
exit(0);
}
/*=========== PARENT PROCESS HANDLER ===========*/
if(myPid != 0)
{
/*Close descriptor for writing on main pipe.*/
close(processes[nProcess + 1 ].fd[W]);
int targetProcess = nProcess + 1; // Target self
bool onGoing = true;
chunk.result = 0;
chunk.time = 0;
while(onGoing)
{
/*Listen from processes if someone ended the task*/
msgCheck = read(processes[nProcess + 1 ].fd[R], &targetProcess, sizeof(int));
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
/*Get result from child process*/
msgCheck = read(processes[nProcess + 1 ].fd[R], &processes[targetProcess].result, sizeof(int));
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
/*Get elapsed time from child process*/
msgCheck = read(processes[nProcess + 1 ].fd[R], &processes[targetProcess].time, sizeof(double));
if(msgCheck < 0)
{
printf("An error occured from the process %d while reading message from parent\n", getpid());
return 1;
}
processes[targetProcess].done = true;
/*Check if remainder to start new task*/
if(remainder != 0)
{
startPoint = taskLenght * nProcess;
processes[targetProcess].done = false;
if (write(processes[targetProcess].fd[W], "sum", MSGSIZE) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
if (write(processes[targetProcess].fd[W], &startPoint, sizeof(int)) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
if (write(processes[targetProcess].fd[W], &remainder, sizeof(int)) < 0)
{
printf("An error occured from the process %d while sending message to parent\n", getpid());
return 1;
}
remainder = 0; //Avoid looping task
}
/*Check for pending response and process final result*/
for (int i = 0; i < nProcess; i++)
{
if(processes[i].done)
{
chunk.result += processes[i].result;
chunk.time += processes[i].time;
onGoing = false;
continue;
}
onGoing = true;
/*Reset total calculations*/
chunk.result = 0;
chunk.time = 0;
break;
}
/*Reset to self target*/
targetProcess = nProcess + 1;
}
printf("Parent calculated: %ld in = %fs\n", processes[nProcess + 1].result, processes[nProcess + 1].time); //! Debug
printf("Processes calculated: %ld in = %fs\n", chunk.result, chunk.time); //! Debug
}
}
cResult arraySum(int start, int length, int* dataset)
{
/*Time unit to calculate the computation time*/
clock_t tic, toc = 0;
cResult chunk = {.result = 0, .time =0};
tic = clock(); // Start measure time
for (int i = start; i < start + length; i++)
{
chunk.result += dataset[i];
}
toc = clock(); // Stop measure time
chunk.time = ((double) (toc - tic))/ CLOCKS_PER_SEC;
printf("start at: %d, length: %d, sum: %ld, in: %f\n", start, length, chunk.result, chunk.time); //! Debug
return chunk;
}
If you want to try this out you'll find in USER DEFINED some variable to play with.
I'll share some of my results, with seed = 1.
LENGTH
PARENT
1 CHILD
5 CHILDREN
10 CHILDREN
1'000
0.000001
0.000002
0.000003
0.000006
100'000
0.000085
0.000107
0.000115
0.000120
10'000'000
0.008693
0.015143
0.016120
0.015982
100'000'000
0.089563
0.148095
0.146698
0.149421
500'000'000
0.669474
0.801828
0.744381
0.816883
As you can see even repeating the task in a single child process in some cases required twice the time.
As ArkadiuszDrabczyk pointed out, it could be a scheduling issue, still why has always to be the child the slowest one?
UPDATE 2:
Since pipe's problems arose, I wrote another source just to exclude these concerns and, of course, the problem remained.
However I wasn't convinced about the deep-copy of the dataset[] array stored on heap hence some research later I found this: What is copy-on-write?. After this new found knowledge, I've added this useless function to be called right before the arraySum() :
void justCheck(int start, int length, int* dataset)
{
for (int i = start; i < start + length; i++)
{
dataset[i] = dataset[i];
}
return;
}
This little snippet managed to level the differences between times. At least now they lay on the same order of magnitude.
Down the results:
LENGTH
PARENT
1 CHILD
5 CHILDREN
10 CHILDREN
1'000
0.000002
0.000001
0.000004
0.000003
100'000
0.000099
0.000110
0.000124
0.000121
10'000'000
0.009496
0.008686
0.009316
0.009248
100'000'000
0.090267
0.092168
0.089862
0.093356
500'000'000
-
-
-
-
Unfortunately this edit rise another problem. With big set of data this function freeze. I know this isn't the right way to force the COW so... Any suggestions?
SHORT: Turns out that in order to prove the thesis of the project I was using the wrong approach. Summing all of the clock times taken by each processes and comparing to the single one is like trying to prove that baking 4 pieces of bread in a row takes less than baking all simultaneously counting the oven time instead of watching my watch. I should have measured the wall time instead of clock time.
LONG: That said, regarding why the same function called by only one child takes more time than parent: As I said in my 2nd update I've managed to trim time to a more plausible value forcing the copy-on-write of used heap on child memory page before calling the actual calculating function. So after several experiments, turns out it's aleatory. Sometimes it's faster, sometimes is almost equal and sometimes slower. So, I thinks it depends on how the task scheduling works, which I don't have control on it neither know how to.
Talking about the other problem mentioned:
Unfortunately this edit rise another problem. With big set of data this function freeze. I know this isn't the right way to force the COW so... Any suggestions?
Well the point is that forcing COW on each processes means that, according to Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne, if I can fit ~
256mln INT value in 1GB of memory the problem is the massive devour of memory my program cause. Checking with my system monitor, I could validate that using 500mln values into my array result in almost 2GB of RAM taken. Which once fork it became equal to nProcess + 1.
I thought to answer these question will help posterity.
Essentially, my program creates 3 threads. The "server" and 2 "workers." The workers are meant to sum the 3 digit positive integers in a 1000 line file (500 numbers per thread). After each worker has summed its part, the server prints each workers total. The only problem is my semaphores are not seeming to work.
Here is a version of my program:
// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
// define semaphores
sem_t s1;
FILE *file;
int sum1 = 0, sum2 = 0, num1 = 0, num2 = 0;
// file name
char fileName[10] = "data1.dat";
// server routine
void* server_routine()
{
printf("Server sent signal to worker thread 1\n");
printf("Server sent signal to worker thread 2\n");
sem_wait(&s1);
printf("Server recieved completion signal from worker thread 1\n");
sem_wait(&s1);
printf("Server recieved completion signal from worker thread 2\n\n");
// print the final results
printf("The sum of the first 500 numbers in the file is: %d\n", sum1);
printf("The sum of the last 500 numbers in the file is: %d\n\n", sum2);
pthread_exit(NULL);
}
// thread 1 reoutine
void* t1_routine()
{
printf("Thread 1 recieved signal from server\n");
file = fopen(fileName, "r");
for(int i = 0; i < 500; i++)
{
fscanf(file, "%d", &num1);
sum1 += num1;
}
printf("sum in thread 1: %d\n", sum1);
printf("Thread 1 sends completion signal to server\n");
sem_post(&s1);
pthread_exit(NULL);
}
// thread 2 routine
void* t2_routine()
{
printf("Thread 2 recieved signal from server\n");
file = fopen(fileName, "r");
fseek(file, 500 * 5, SEEK_SET);
for(int i = 0; i < 500; i++)
{
fscanf(file, "%d", &num2);
sum2 += num2;
}
printf("sum in thread 2: %d\n", sum2);
printf("Thread 2 sends completion signal to server\n");
sem_post(&s1);
pthread_exit(NULL);
}
// main function
int main(int argc, char *argv[])
{
// define threads
pthread_t server, t1, t2;
// initialize the semaphore
sem_init(&s1, 0, 0);
if(pthread_create(&server, NULL, &server_routine, NULL) != 0)
{
return 1;
}
if(pthread_create(&t1, NULL, &t1_routine, NULL) != 0)
{
return 2;
}
if(pthread_create(&t2, NULL, &t2_routine, NULL) != 0)
{
return 3;
}
if(pthread_join(server, NULL) != 0)
{
return 4;
}
if(pthread_join(t1, NULL) != 0)
{
return 5;
}
if(pthread_join(t2, NULL) != 0)
{
return 6;
}
// destroy semaphores
sem_close(&s1);
// exit thread
pthread_exit(NULL);
// end
return 0;
}
I've tested with less threads more semaphores as well, with non luck. I've tried different initial semaphore values. The only time I can get the correct output is when I manually wait with sleep(5); but that defeats the purpose of this project.
A few issues ...
Each client thread does its own/private fopen but FILE *file; is global so they overwrite each others values.
We need to make this variable function scoped so each thread has its own private pointer.
There are no fclose calls.
pthread_exit should not be done by the main thread. It is only for threads created with pthread_create.
Otherwise ...
Whichever thread does the fopen last will set the final value.
So, there is a race condition and the effect is the same as if the main thread (prior to pthread_create calls) had done a single fopen, defeating the purpose of each thread doing its own fopen.
Worse yet, a given thread may do the first fopen, then start with fscanf and have its file value changed when the second thread replaces the file value, so weird stuff happens to each thread because they are doing fseek/fscanf on the same FILE * instance.
Having the aforementioned fclose calls would have made the issue more evident.
Here is the refactored code. It is annotated:
// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
// define semaphores
sem_t s1;
// NOTE/BUG: each thread opens a different stream, so this must be function
// scoped
#if 0
FILE *file;
#endif
int sum1 = 0,
sum2 = 0,
num1 = 0,
num2 = 0;
// file name
char fileName[10] = "data1.dat";
// server routine
void *
server_routine()
{
printf("Server sent signal to worker thread 1\n");
printf("Server sent signal to worker thread 2\n");
sem_wait(&s1);
printf("Server recieved completion signal from worker thread 1\n");
sem_wait(&s1);
printf("Server recieved completion signal from worker thread 2\n\n");
// print the final results
printf("The sum of the first 500 numbers in the file is: %d\n", sum1);
printf("The sum of the last 500 numbers in the file is: %d\n\n", sum2);
pthread_exit(NULL);
}
// thread 1 reoutine
void *
t1_routine()
{
// NOTE/FIX: this must be function scoped (i.e. private to this thread)
#if 1
FILE *file;
#endif
printf("Thread 1 recieved signal from server\n");
file = fopen(fileName, "r");
for (int i = 0; i < 500; i++) {
fscanf(file, "%d", &num1);
sum1 += num1;
}
printf("sum in thread 1: %d\n", sum1);
printf("Thread 1 sends completion signal to server\n");
sem_post(&s1);
#if 1
fclose(file);
#endif
pthread_exit(NULL);
}
// thread 2 routine
void *
t2_routine()
{
// NOTE/FIX: this must be function scoped (i.e. private to this thread)
#if 1
FILE *file;
#endif
printf("Thread 2 recieved signal from server\n");
file = fopen(fileName, "r");
fseek(file, 500 * 5, SEEK_SET);
for (int i = 0; i < 500; i++) {
fscanf(file, "%d", &num2);
sum2 += num2;
}
printf("sum in thread 2: %d\n", sum2);
printf("Thread 2 sends completion signal to server\n");
sem_post(&s1);
#if 1
fclose(file);
#endif
pthread_exit(NULL);
}
// main function
int
main(int argc, char *argv[])
{
// define threads
pthread_t server, t1, t2;
// initialize the semaphore
sem_init(&s1, 0, 0);
if (pthread_create(&server, NULL, &server_routine, NULL) != 0) {
return 1;
}
if (pthread_create(&t1, NULL, &t1_routine, NULL) != 0) {
return 2;
}
if (pthread_create(&t2, NULL, &t2_routine, NULL) != 0) {
return 3;
}
if (pthread_join(server, NULL) != 0) {
return 4;
}
if (pthread_join(t1, NULL) != 0) {
return 5;
}
if (pthread_join(t2, NULL) != 0) {
return 6;
}
// destroy semaphores
sem_close(&s1);
// exit thread
// NOTE/BUG: only a subthread should do this
#if 0
pthread_exit(NULL);
#endif
// end
return 0;
}
In the code above, I've used cpp conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Note: this can be cleaned up by running the file through unifdef -k
UPDATE:
Thank you for the response Craig. I have implemented your suggestions to my own code but nothing seemed to change. I then decided to copy paste your updated code into a c file to test it out and I got the same result. It is as follows (in a separate comment since the output is too long): –
Max
It's hard to compare results because we're using different datasets. I created a perl script to create some data.
Most important is that the sum reported by the given worker matches what the server sees for that worker task.
Then, if we know what each per thread section of the file should sum to, that is another matter.
The per line termination is critical (e.g.): CRLF vs LF (see below)
The actual order of worker sem_post and termination doesn't really matter. It can vary system to system or, even, invocation to invocation. What matters is that the server thread waits for N threads (i.e.) N sem_wait calls before printing any sums.
I've produced an updated version below.
Server does not "signal" a worker. The worker "signals" the server by doing sem_post and the server "receives" it by doing sem_wait
I've create a task/thread struct to hold the sums, thread IDs, etc.
I've generalized the code to allow N threads.
Added check of \n placement (i.e. line width). That is, under linux/POSIX a four digit number would be followed by LF (newline) and length would be 5. But, under windows, it would be CRLF (carriage return/newline) and length would be 6.
Added check of file size to ensure it is exactly the desired/expected length.
Some additional diagnostics.
Here is the updated code:
// simple c program to simulate POSIX thread and semaphore
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>
// number of bytes per line
// 5: 4 digits + LF
// 6: 4 digits + CRLF
#ifndef LINEWID
#define LINEWID (4 + 1)
#endif
// number of items / task
#ifndef COUNT
#define COUNT 500
#endif
// define semaphores
sem_t s1;
#if 0
int sum1 = 0,
sum2 = 0,
num1 = 0,
num2 = 0;
#endif
// file name
#if 0
char fileName[10] = "data1.dat";
#else
const char *fileName = "data1.dat";
#endif
// task control
typedef struct {
pthread_t tid; // thread ID
int tno; // thread index/offset
int sum; // sum
} tsk_t;
#define TSKMAX 50
int tskmax; // actual number of tasks
tsk_t tsklist[TSKMAX]; // list of tasks
// loop through all task blocks
#define TSKFORALL \
tsk_t *tsk = &tsklist[0]; tsk < &tsklist[tskmax]; ++tsk
// server routine
void *
server_routine(void *vp)
{
// NOTE/BUG: server does _not_ signal worker
#if 0
printf("Server sent signal to worker thread 1\n");
printf("Server sent signal to worker thread 2\n");
#endif
for (TSKFORALL) {
printf("Server waiting ...\n");
sem_wait(&s1);
printf("Server complete ...\n");
}
// print the final results
for (TSKFORALL)
printf("The sum of task %d is %d\n",tsk->tno,tsk->sum);
return (void *) 0;
}
// thread 1 reoutine
void *
worker_routine(void *vp)
{
FILE *file;
tsk_t *tsk = vp;
printf("Thread %d running ...\n",tsk->tno);
file = fopen(fileName, "r");
fseek(file,tsk->tno * COUNT * LINEWID,SEEK_SET);
tsk->sum = 0;
int num1;
int first = -1;
int last = -1;
for (int i = 0; i < COUNT; i++) {
if (fscanf(file, "%d", &num1) != 1) {
printf("Thread %d fscan error\n",tsk->tno);
break;
}
if (i == 0)
first = num1;
if (i == (COUNT - 1))
last = num1;
tsk->sum += num1;
}
printf("sum in thread %d: %d (first %d, last %d)\n",
tsk->tno, tsk->sum, first, last);
sem_post(&s1);
#if 1
fclose(file);
#endif
return (void *) 0;
}
// main function
int
main(int argc, char **argv)
{
--argc;
++argv;
setlinebuf(stdout);
setlinebuf(stderr);
if (argc != 1)
tskmax = 2;
else
tskmax = atoi(*argv);
if (tskmax > TSKMAX)
tskmax = TSKMAX;
// define threads
pthread_t server;
printf("main: %d tasks\n",tskmax);
printf("main: %d count\n",COUNT);
FILE *file = fopen(fileName,"r");
if (file == NULL) {
printf("main: fopen failure\n");
exit(96);
}
// check alignment
char chr;
fseek(file,LINEWID - 1,0);
fread(&chr,1,1,file);
if (chr != '\n') {
printf("main: newline mismatch -- chr=%2.2X\n",chr);
exit(97);
}
// get the file size
struct stat st;
if (fstat(fileno(file),&st) < 0) {
printf("main: fstat fault\n");
exit(97);
}
// ensure the file has the correct size
off_t size = tskmax * LINEWID * COUNT;
if (st.st_size != size)
printf("main: wrong file size -- st_size=%llu size=%llu\n",
(unsigned long long) st.st_size,
(unsigned long long) size);
fclose(file);
// initialize the semaphore
sem_init(&s1, 0, 0);
// set the offsets
int tno = 0;
for (TSKFORALL, ++tno)
tsk->tno = tno;
if (pthread_create(&server, NULL, &server_routine, NULL) != 0)
return 98;
for (TSKFORALL) {
if (pthread_create(&tsk->tid,NULL,worker_routine,tsk) != 0)
return 1 + tsk->tno;
}
if (pthread_join(server, NULL) != 0) {
return 99;
}
for (TSKFORALL) {
if (pthread_join(tsk->tid, NULL) != 0) {
return 5;
}
}
// destroy semaphores
sem_close(&s1);
// end
return 0;
}
Here is the perl script output that I used to generate the data:
number of tasks 2
element count per task 500
line separater 0A
section 0 sum 124750
section 1 sum 125250
Here is the program output:
main: 2 tasks
Server waiting ...
Thread 0 running ...
Thread 1 running ...
sum in thread 1: 125250 (first 1, last 500)
sum in thread 0: 124750 (first 0, last 499)
Server complete ...
Server waiting ...
Server complete ...
The sum of task 0 is 124750
The sum of task 1 is 125250
Here is the perl script:
#!/usr/bin/perl
# gendata -- generate data
#
# arguments:
# 1 - number of tasks (DEFAULT: 2)
# 2 - number of items / task (DEFAULT: 500)
# 3 - line separater (DEFAULT: \n)
master(#ARGV);
exit(0);
# master -- master control
sub master
{
my(#argv) = #_;
$tskmax = shift(#argv);
$tskmax //= 2;
printf(STDERR "number of tasks %d\n",$tskmax);
$count = shift(#argv);
$count //= 500;
printf(STDERR "element count per task %d\n",$count);
$sep = shift(#argv);
$sep //= "\n";
printf(STDERR "line separater");
foreach $chr (split(//,$sep)) {
$hex = ord($chr);
printf(STDERR " %2.2X",$hex);
}
printf(STDERR "\n");
for ($itsk = 0; $itsk < $tskmax; ++$itsk) {
$val = $itsk;
$sum = 0;
for ($lno = 1; $lno <= $count; ++$lno, ++$val) {
printf("%4d%s",$val,$sep);
$sum += $val;
}
printf(STDERR "section %d sum %d\n",$itsk,$sum);
}
}
My program has a consumer and multiple producers. The producers each read a different file and write their content into a FIFO in N-sized chunks, with a leading parameter for the consumer to interpret.
The consumer is supposed to take these chunks and compose an output file where each line corresponds to one producer. The leading parameter from the chunk is used to determine the owner of the chunk and where to write it (it's a line number number in the output file).
My problem is, even though it works mostly fine when there's one producer, any more make the resulting file a mess. Also there are some unexpected excessive \n but they aren't critical.
This is my expected output:
aaaaa1a aaaaaaa2a aaa3a aaaaaaaaaaa4a
bbbbbbbbbbb1b bbbbbbb2b bbbbbbbbbbbbbb3b bbbbbbb4b bbbbbbbbbb5b bb6b
cccccccccc1c cccc2c cccccccc3c ccccc4c ccccccccc5c ccccccccccccc6c
but that's what I get:
aaaaa1a aaaaaaa2a aaa3a aaaaaaaaaaa4a2 bbbbbbb43 cccccccc53 cccccccc2 bbbbbbbb2 b5b bb6b3 cccc6c2
bbbbbbbbbbb1b bbbbbbb2b bbbbbbbbbbbbbb3b
cccccccccc1c cccc2c cccccccc3c ccccc4c c
There's an unexpected cutoff in the later lines and the chunks become mixed up.
I think it's a problem with how I handle the named pipes, because I'm printing the "raw input" before further processing and I can see that I'm reading invalid data from the pipe. But AFAIK Linux has atomic writes for small chunks of data for FIFO. Maybe the reads aren't caring about the writes and that's where lies the problem?
Consumer code:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
char *filename;
size_t getFileSize(FILE *fp) {
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
rewind(fp);
printf("length %ld \n", len);
return len;
}
int nthFunctionCall = 1;
void printFile(FILE *file) {
char *fileContent = NULL;
if(file != NULL) {
size_t size = getFileSize(file);
fileContent = malloc((size /* + 1*/) * sizeof(char));
fread(fileContent, 1, size, file);
//fileContent[size + 1] = '\0'; ?
}
printf("FILE CONTENT: \n%s\n", fileContent);
}
void writeToFile(long targetLineNumber, char *text) {
FILE *temp = fopen("temp", "w");
if(temp == NULL) {
perror("can't create temp");
exit(-1);
}
char *fileContents = NULL;
FILE *file = fopen(filename, "r");
if(file != NULL) {
size_t size = getFileSize(file);
fileContents = malloc((size + 1) * sizeof(char));
fread(fileContents, 1, size, file);
fileContents[size] = '\0'; // tbh, I don't know whether I should do this or not.
fclose(file);
}
char *fileContentsCpy = fileContents;
printf("FILE CONTENT:\n %s\n", fileContents);
printf("%d Text to save %s\n", nthFunctionCall, text);
char *currentLineFromFile;
size_t processedLineNumber;
for (processedLineNumber = 1; (currentLineFromFile = strsep(&fileContents, "\n")) != NULL; processedLineNumber++) {
printf("%d targetLineNumber %ld processedLineNumber %ld \n", nthFunctionCall, targetLineNumber, processedLineNumber);
printf("%d copy the current line into temp: %s\n", nthFunctionCall, currentLineFromFile);
fputs(currentLineFromFile, temp);
if(processedLineNumber == targetLineNumber) {
printf("%d add text to line %ld: %s\n", nthFunctionCall, processedLineNumber, text);
fputs(text, temp);
}
fputs("\n", temp);
fflush(temp);
}
printf("%d Finished loop with: targetLineNumber %ld processedLineNumber %ld \n", nthFunctionCall, targetLineNumber, processedLineNumber);
if(targetLineNumber >= processedLineNumber) {
for (int j = 0; j < (targetLineNumber - processedLineNumber); ++j) {
fputs("\n", temp);
}
printf("%d added text: %s\n", nthFunctionCall, text);
fputs(text, temp);
fflush(temp);
}
fclose(temp);
if(fileContentsCpy != NULL) free(fileContentsCpy);
nthFunctionCall++;
remove(filename);
rename("temp", filename);
printf("One iteration end\n");
}
int numberLength(size_t number) {
int len = 0;
while(number > 0) {
number /= 10;
len++;
}
return len;
}
int main(int argc, char **argv)
{
if (argc < 4) {
fprintf(stderr, "testConsument <fifo_path> <file_to_save_in> <chunk size>\n");
exit(-1);
}
char *myfifo = argv[1];
filename = argv[2];
int numberToRead = atoi(argv[3]);
int fd = open(myfifo, O_RDONLY);
perror("sdada test consument");
char *str1 = calloc(100, sizeof(char));
while (read(fd, str1, numberToRead + 3) > 0) {
long lineNumber;
printf("length: %ld raw input: %s\n", strlen(str1), str1);
sscanf(str1, "%ld", &lineNumber);
char* content = str1 + numberLength(lineNumber) + 1; // lines should be of the format "<number> <chunk-sized-word>\0"
printf("add to line %ld content : %s \n", lineNumber, content);
writeToFile(lineNumber, content);
sleep(1);
free(str1);
str1 = calloc(100, sizeof(char));
printf("#################\n");
}
free(str1);
close(fd);
FILE *res = fopen(filename, "r");
printFile(res);
fclose(res);
return 0;
}
Producer code:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
size_t getFileSize(FILE *fp) {
fseek(fp, 0, SEEK_END);
size_t len = ftell(fp);
rewind(fp);
return len;
}
int main(int argc, char **argv) {
if (argc < 5) {
fprintf(stderr, "producer <fifo_path> <line_number_to_save_in> <input_file> <chunk_size>\n");
exit(-1);
}
char *myfifo = argv[1];
char *lineNumber = argv[2];
int numberToRead = atoi(argv[4]);
mkfifo(myfifo, 0666);
int fd = open(myfifo, O_WRONLY);
char *someFilePath = argv[3];
FILE *somFile = fopen(someFilePath, "r");
char *buf = calloc(numberToRead, sizeof(char));
size_t size = 1;
while ((fread(buf, size, numberToRead, somFile) > 0)) {
char *buf2 = calloc((numberToRead + 3), sizeof(char));
strcat(buf2, lineNumber); strcat(buf2, " "); strcat(buf2, buf); strcat(buf2, "\0");
while (strstr(buf2, "\n")) {
buf2[strcspn(buf2, "\n")] = ' ';
}
printf("SENDING: %s\n", buf2);
fflush(stdout);
write(fd, buf2, numberToRead + 3);
sleep(2);
free(buf);
free(buf2);
buf = calloc(numberToRead, sizeof(char));
}
write(fd, lineNumber, 2);
close(fd);
return 0;
}
After running both the producer and the consumer the communication should start working and after some time there should be an output file. After each such execution you have to manually remove the file, because I didn't really consider the situation where it has existed before.
Example start (each line should be in a different terminal):
./producer '/tmp/fifo3' 3 'file1' 10
./producer '/tmp/fifo3' 2 'file1' 10
./producer '/tmp/fifo3' 1 'file1' 10
./testConsument '/tmp/fifo3' 'output' 10
There are a lot of debug prints, I'm not sure if they are helpful or not but I'm leaving them in.
The problem you face is that having several producers attached to a shared resource (the fifo) you need to control how the accesses are done to be able to control that the consumer gets the data in the proper sequence. The only help you get from the kernel is at the write(2) system call level (the kernel locks the inode of the destination fifo during the time the system call is being executed) So, if you are making short writes, the easiest approach is to group together all the data you are going to put in the fifo and write(2) it all in one single call to write.
If you opt for a more complex solution, then you need to use some kind of mutex/semaphore/whatever to control who has exclusive access to the fifo for writting, as other processes must wait for it to release the lock before starting to write.
Also, don't try to use stdio if you are using this approach. The stdio package only writes data when flushing a buffer, and this happens differently for the output terminal than for a fifo, it depends on the actua buffer size it is using and you don't have an exact and clear idea on when it is happening. This means you cannot use fprintf(3) and friends.
Finally, if you use the atomicity of write(2), then have in mind that a fifo is a limited resource, that can buffer data, and will break a write(2) call if you try to write a big amount of data in one shot (this meaning a single write(2) call) you can get a partial write and you will not be able to recover from this because in the mean time other producers can have access to the fifo and be writting on it (which will break your writting structure) As a rule of thumb, try to recude your messages to a small number of kilobytes (4kb or 8kb being a good upper limit, to be portable to different unices)
The problem laid with the line
write(fd, lineNumber, 2);
near the end in the producer program. It sent unnecessary data which wasn't meaningful in any way and wasn't interpreted properly.
After removing it the program works as intended (except for the unexpected new lines, but they aren't that bad and they have happened before).
I have this code that reads in two int numbers from a file. And stores it in a buffer[] to be taken in a second function to be used. I'm not sure if my stopping conditions in the first function are correct. It looks fine but when running the code it stops at the second function.
static int count = 0;
int buffer[5];
int requestNum = 1;
FILE* f;
int main(int argc, char** argv)
{
/*THREAD IDs*/
pthread_t liftR, lift1;
/*OPEN FILE*/
f = fopen("sim_input.txt", "r");
/*CREATE THREAD*/
pthread_create(&liftR, NULL, request, NULL);
pthread_create(&lift1, NULL, lift, NULL);
/*RUNS TILL THREADS FINISHED*/
pthread_join(liftR, NULL);
pthread_join(lift1, NULL);
/*CLEAN UP*/
fclose(f);
return 0;
}
void* request(void* data1)
{
int req1, req2, eof;
/*NOT EOF*/
while(eof != -1)
{
/*READ ONE REQUEST*/
eof = fscanf(f, "%d %d", &req1, &req2);
/*CHECK IF BUFFER FULLL*/
if(count < 5)
{
/*ADD REQUEST TO BUFFER*/
buffer[count] = req1;
buffer[count + 1] = req2;
count = count + 2;
printf("COUNT: %d\n", count);
/*PRINTING*/
printf("-------------------------------------------\n");
printf("From Buffer -> Item1: %d, Item2: %d\n", req1, req2);
printf("Request No: %d\n", requestNum);
printf("-------------------------------------------\n");
requestNum++;
}
}
return NULL;
}
void* lift(void* data2)
{
while(count > 0)
{
sleep(1);
printf("================\n");
printf("COUNT: %d\n", count);
printf("REMOVE ITEM FROM BUFFER - DO STUFF WITH IT\n");
printf("================\n");
count = count - 2;
}
return NULL;
}
OUTPUT:
Shows count 2, 4, 6. Only 3 request shown, In file it goes up to 10 request
There are multiple bugs in your program:
Reading uninitialized eof variable:
int int req1, req2, eof; // What is the value of eof? It could be -1 (or anything else).
/*NOT EOF*/
while(eof != -1)
You throw away the data you read:
if(count < 5)
{
/*ADD REQUEST TO BUFFER*/
}
If the first thread runs for a while before the second thread starts, then it will store the first two requests into the buffer, and throw away the rest.
To fix this, you need to wait for the second thread to drain the buffer when it is full.
You access count without any locking, which is a data race and undefined behavior. You must protect reading and writing shared (between threads) globals with a mutex.
I am using multiple threads to access various random files using threads. However, I get an error [Thread 0x7ffff7007700 (LWP 16256) exited]. Also, info threads shows that only 2 threads are created. However, I am trying to create 100 of them. Also, do I have to use the pthread_join() function in my case? The code:
#define NTHREADS 100
void *encrypt(void *args)
{
int count = *((int*) args);
AES_KEY enc_key;
AES_set_encrypt_key(key, 128, &enc_key);
int i;
for(i=1;i<=count;i++){
char *ifile;
char *ofile;
long length;
size_t result;
char * buffer;
sprintf(ifile,"random_files/random_%d.txt",i);
FILE *ifp = fopen(ifile,"rb");
if (ifp==NULL) {fputs ("File error",stderr); exit (1);}
fseek(ifp,0, SEEK_END);
length = ftell(ifp);
fseek (ifp,0, SEEK_SET);
buffer = (char*) malloc (sizeof(char)*length);
if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
result = fread (buffer, 1, length, ifp);
if (result != length) {fputs ("Reading error",stderr); exit (3);}
printf("%s",buffer);
fclose (ifp);
free(buffer);
}
}
int main(){
int i,j,count =0;
pthread_t threads[NTHREADS];
for (i=0; i<NTHREADS; i++){
count = count +20;
int *count_ptr = &count;
if(pthread_create(&threads[i], NULL, encrypt, count_ptr)!=0){
fprintf(stderr, "error: Cannot create thread # %d\n", i);
break;
}
}
printf("After Thread\n");
exit(0);
}
Yes, you should join your threads, as none were created detached (and you're probably not deep enough in learning pthreads to deal with that anyway).
That said, you have a significant logic problem in your thread parameters. They're all getting the address of the same count variable in main. Probably the fastest way to change that with no modifications to your thread-proc is simply stand up a side-by-side array of int counts[NTHREADS] matching your threads, using each element as the corresponding thread's data param:
int main()
{
pthread_t threads[NTHREADS];
int counts[NTHREADS]; // HERE
int i,j,count =0;
for (i=0; i<NTHREADS; i++)
{
counts[i] = (count += 20);
if(pthread_create(&threads[i], NULL, encrypt, counts+i)!=0) // LAST PARAM
{
fprintf(stderr, "error: Cannot create thread # %d\n", i);
break;
}
}
for (i=0; i<NTHREADS; ++i)
pthread_join(threads[i], NULL));
return EXIT_SUCCESS;
}
Alternatively, you could do some dynamic allocation hoops, or send the value via intptr_t, cast to void*, but the method shown above has the advantage of requiring no changes on your thread-proc, a target I was aiming for.
I've left any issues in your thread proc to you to solve, but that should get you up and running on your thread stack, at least.
There are missing things in the code, first, in your encrypt function you need to close the threads at the end and also in the main function BUT, before you do so, after your for loop you need to join them all, then they are going to be processed and closed correctly.
The flow will go like this:
(void) pthread_join(th1, NULL);
(void) pthread_join(th2, NULL);
You can put them all in a for to join them by tid.
Another update: There is not really anything "random" going on, I would just start open the files according to its tid, and then add other big implementations, your as is will try to open files like crazy, it should work anyways but, just saying.