I only just started writing multithreading in C and don't have a full understanding of how to implement it. I'm writing a code that reads an input file and puts into a buffer struct array. When the buffer has no more available space, request_t is blocked waiting for available space. It is controlled by thread Lift_R. The other threads lift 1-3 operate lift() and it writes whats in buffer to the output file depending number of int sec. sec and size and given values through command line. This will free up space for request to continue reading the input.
Can someone please help me with how to implement these functions properly. I know there are other questions relating to this, but I want my code to meet specific conditions.
(NOTE: lift operates in FIFO and threads use mutual exclusion)
This is what I wrote so far, I haven't implemented any waiting conditions or FIFO yet, I'm currently focusing on the writing to file and debugging and am soon getting to wait and signal.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "list.h"
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; //declare thread conditions
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //declare mutex
int sec; //time required by each lift to serve a request
int size; //buffer size
buffer_t A[];
write_t write;
void *lift(void *vargp)
{
pthread_mutex_lock(&lock);
FILE* out;
out = fopen("sim_output.txt", "w");
//gather information to print
if (write.p == NULL) //only for when system begins
{
write.p = A[1].from;
}
write.rf = A[1].from;
write.rt = A[1].to;
write.m = (write.p - A[1].from) + (A[1].to - A[1].from);
if (write.total_r == NULL) //for when the system first begins
{
write.total_r = 0;
}
else
{
write.total_r++;
}
if (write.total_m == NULL)
{
write.total_m = write.m;
}
else
{
write.total_m = write.total_m + write.m;
}
write.c = A[1].to;
//Now write the information
fprintf(out, "Previous position: Floor %d\n", write.p);
fprintf(out, "Request: Floor %d to Floor %d\n", write.rf, write.rt);
fprintf(out, "Detail operations:\n");
fprintf(out, " Go from Floor %d to Floor %d\n", write.p, write.rf);
fprintf(out, " Go from Floor %d to Floor %d\n", write.rf, write.rt);
fprintf(out, " #movement for this request: %d\n", write.m);
fprintf(out, " #request: %d\n", write.total_r);
fprintf(out, " Total #movement: %d\n", write.total_m);
fprintf(out, "Current Position: Floor %d\n", write.c);
write.p = write.c; //for next statement
pthread_mutex_unlock(&lock);
return NULL;
}
void *request_t(void *vargp)
{
pthread_mutex_lock(&lock); //Now only request can operate
FILE* f;
FILE* f2;
f = fopen("sim_input.txt", "r");
if (f == NULL)
{
printf("input file empty\n");
exit(EXIT_FAILURE);
}
f2 = fopen("sim_output.txt", "w");
int i = 0;
for (i; i < size; i++)
{
//read the input line by line and into the buffer
fscanf(f, "%d %d", &A[i].from, &A[i].to);\
//Print buffer information to sim_output
fprintf(f2, "----------------------------\n");
fprintf(f2, "New Lift Request from Floor %d to Floor %d \n", A[i].from, A[i].to);
fprintf(f2, "Request No %d \n", i);
fprintf(f2, "----------------------------\n");
}
printf("Buffer is full");
fclose(f);
fclose(f2);
pthread_mutex_unlock(&lock);
return NULL;
}
void main(int argc, char *argv[]) // to avoid segmentation fault
{
size = atoi(argv[0]);
if (!(size >= 1))
{
printf("buffer size too small\n");
exit(0);
}
else
{
A[size].from = NULL;
A[size].to = NULL;
}
sec = atoi(argv[1]);
pthread_t Lift_R, lift_1, lift_2, lift_3;
pthread_create(&Lift_R, NULL, request_t, NULL);
pthread_join(Lift_R, NULL);
pthread_create(&lift_1, NULL, lift, NULL);
pthread_join(lift_1, NULL);
pthread_create(&lift_2, NULL, lift, NULL);
pthread_join(lift_2, NULL);
pthread_create(&lift_3, NULL, lift, NULL);
pthread_join(lift_3, NULL);
}
And here is the struct files:
#include <stdbool.h>
typedef struct Buffer
{
int from;
int to;
}buffer_t; //buffer arrary to store from and to values from sim_input
typedef struct Output
{
int l; //lift number
int p; //previous floor
int rf; //request from
int rt; //request to
int total_m; //total movement
int c; // current position
int m; //movement
int total_r; //total requests made
}write_t;
Between reading your code and question I see a large conceptual gap. There are some technical problems in the code (eg. you never fclose out); and a hard to follow sequence.
So, this pattern:
pthread_create(&x, ?, func, arg);
pthread_join(x, ...);
Can be replaced with:
func(arg);
so, your really aren't multithreaded at all; it is exactly as if:
void main(int argc, char *argv[]) // to avoid segmentation fault
{
size = atoi(argv[0]);
if (!(size >= 1))
{
printf("buffer size too small\n");
exit(0);
}
else
{
A[size].from = NULL;
A[size].to = NULL;
}
sec = atoi(argv[1]);
request_t(0);
lift(0);
lift(0);
lift(0);
}
and, knowing that, I hope you can see the futility in:
pthread_mutex_lock(&lock);
....
pthread_mutex_unlock(&lock);
So, start with a bit of a rethink of what you are doing. It sounds like you have a lift device which needs to take inbound requests, perhaps sort them, then process them. Likely 'forever'.
This probably means a sorted queue; however one not sorted by an ordinary criteria. The lift traverses the building in both directions, but means to minimize changes in direction. This involves traversing the queue with both an order ( >, < ) and a current-direction.
You would likely want request to simply evaluate the lift graph, and determine where to insert the new request.
The lift graph would be a unidirectional list of where the lift goes next. And, perhaps a rule that the list only consults its list as it stops at a given floor.
So, the Request can take a lock of the graph, alter it to reflect the new requestion, then unlock it.
The Lift can simply:
while (!Lift_is_decommissioned) {
pthread_mutex_lock(&glock);
Destination = RemoveHead(&graph);
pthread_mutex_unlock(&glock);
GoTo(Destination);
}
And the Request can be:
pthread_mutex_lock(&glock);
NewDestination = NewEvent.floorpressed;
NewDirection = NewEvent.floorpressed > NewEvent.curfloor ? Up : Down;
i = FindInsertion(&graph, NewDestination, NewDirection);
InsertAt(&graph, i, NewDestination);
pthread_mutex_unlock(&glock);
Which may be a bit surprising that there is no difference between pressing a "goto floor" button from within the lift, and a "I want lift here now" from outside the lift.
But, with this sort of separation, you can have the lift simply follow the recipe above, and the handlers for the buttons invoke the other pseudo code above.
The FindInsertion() may be a bit hairy though....
Related
I wrote this program to solve the dining philosophers problem using Dijkstra's algorithm, notice that I'm using an array of booleans (data->locked) instead of an array of binary semaphores.
I'm not sure if this solution is valid (hence the SO question).
Will access to the data->locked array in both test and take_forks functions cause data races? if so is it even possible to solve this problem using Dijkstra's algorithm with only mutexes?
I'm only allowed to use mutexes, no semaphores, no condition variables (it's an assignment).
Example of usage:
./a.out 4 1000 1000
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#define NOT_HUNGRY 1
#define HUNGRY 2
#define EATING 3
#define RIGHT ((i + 1) % data->n)
#define LEFT ((i + data->n - 1) % data->n)
typedef struct s_data
{
int n;
int t_sleep;
int t_eat;
int *state;
bool *locked;
pthread_mutex_t *state_mutex;
} t_data;
typedef struct s_arg
{
t_data *data;
int i;
} t_arg;
int ft_min(int a, int b)
{
if (a < b)
return (a);
return (b);
}
int ft_max(int a, int b)
{
if (a > b)
return (a);
return (b);
}
// if the LEFT and RIGHT threads are not eating
// and thread number i is hungry, change its state to EATING
// and signal to the while loop in `take_forks` to stop blocking.
// if a thread has a state of HUNGRY then it's guaranteed
// to be out of the critical section of `take_forks`.
void test(int i, t_data *data)
{
if (
data->state[i] == HUNGRY
&& data->state[LEFT] != EATING
&& data->state[RIGHT] != EATING
)
{
data->state[i] = EATING;
data->locked[i] = false;
}
}
// set the state of the thread number i to HUNGRY
// and block until the LEFT and RIGHT threads are not EATING
// in which case they will call `test` from `put_forks`
// which will result in breaking the while loop
void take_forks(int i, t_data *data)
{
pthread_mutex_lock(data->state_mutex);
data->locked[i] = true;
data->state[i] = HUNGRY;
test(i, data);
pthread_mutex_unlock(data->state_mutex);
while (data->locked[i]);
}
// set the state of the thread number i to NOT_HUNGRY
// then signal to the LEFT and RIGHT threads
// so they can start eating when their neighbors are not eating
void put_forks(int i, t_data *data)
{
pthread_mutex_lock(data->state_mutex);
data->state[i] = NOT_HUNGRY;
test(LEFT, data);
test(RIGHT, data);
pthread_mutex_unlock(data->state_mutex);
}
void *philosopher(void *_arg)
{
t_arg *arg = _arg;
while (true)
{
printf("%d is thinking\n", arg->i);
take_forks(arg->i, arg->data);
printf("%d is eating\n", arg->i);
usleep(arg->data->t_eat * 1000);
put_forks(arg->i, arg->data);
printf("%d is sleeping\n", arg->i);
usleep(arg->data->t_sleep * 1000);
}
return (NULL);
}
void data_init(t_data *data, pthread_mutex_t *state_mutex, char **argv)
{
int i = 0;
data->n = atoi(argv[1]);
data->t_eat = atoi(argv[2]);
data->t_sleep = atoi(argv[3]);
pthread_mutex_init(state_mutex, NULL);
data->state_mutex = state_mutex;
data->state = malloc(data->n * sizeof(int));
data->locked = malloc(data->n * sizeof(bool));
while (i < data->n)
{
data->state[i] = NOT_HUNGRY;
data->locked[i] = true;
i++;
}
}
int main(int argc, char **argv)
{
pthread_mutex_t state_mutex;
t_data data;
t_arg *args;
pthread_t *threads;
int i;
if (argc != 4)
{
fputs("Error\nInvalid argument count\n", stderr);
return (1);
}
data_init(&data, &state_mutex, argv);
args = malloc(data.n * sizeof(t_arg));
i = 0;
while (i < data.n)
{
args[i].data = &data;
args[i].i = i;
i++;
}
threads = malloc(data.n * sizeof(pthread_t));
i = 0;
while (i < data.n)
{
pthread_create(threads + i, NULL, philosopher, args + i);
i++;
}
i = 0;
while (i < data.n)
pthread_join(threads[i++], NULL);
}
Your spin loop while (data->locked[i]); is a data race; you don't hold the lock while reading it data->locked[i], and so another thread could take the lock and write to that same variable while you are reading it. In fact, you rely on that happening. But this is undefined behavior.
Immediate practical consequences are that the compiler can delete the test (since in the absence of a data race, data->locked[i] could not change between iterations), or delete the loop altogether (since it's now an infinite loop, and nontrivial infinite loops are UB). Of course other undesired outcomes are also possible.
So you have to hold the mutex while testing the flag. If it's false, you should then hold the mutex until you set it true and do your other work; otherwise there is a race where another thread could get it first. If it's true, then drop the mutex, wait a little while, take it again, and retry.
(How long is a "little while", and what work you choose to do in between, are probably things you should test. Depending on what kind of fairness algorithms your pthread implementation uses, you might run into situations where take_forks succeeds in retaking the lock even if put_forks is also waiting to lock it.)
Of course, in a "real" program, you wouldn't do it this way in the first place; you'd use a condition variable.
I have been working out an example to demonstrate multithreading using POSIX on processing operation of an image. The encoding was done with lodepng library. I wanted the code to break a color inverse process of any loaded image into 4 equal threads. Although I have checked repeatedly, I still cant find out why the 4th thread starting and ending values are getting modified inside the threadFunc2() function. Its the same code used by other 3 threads getting correct start and end values. I have used a structure to pass start, end and thread number data into function. For example, if I load a 10x10 image which results in 100 pixels (each with 4 values for R,G, B and Transparency),it requires a 400 element array to store each color value. The 4th thread should start with 300 and 399. Its correctly calculated in the main() function before passing to function, but ends up as being 5 and 6 within the threadFunc2() at start.
#include <pthread.h>
#include "lodepng.h"
#include <stdio.h>
#include <stdlib.h>
#include "lodepng.c"
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
unsigned int error;
unsigned int encError;
unsigned char* image;
unsigned int width;
unsigned int height;
int Arraysize;
const char* filename = "10x10Circle.png";
const char* newFileName = "10x10Result.png";
struct Markers{int start;int end;int no};
void *threadFunc(void *arg){ // function to load the image into an array
pthread_mutex_lock(&mutex);
error = lodepng_decode32_file(&image, &width, &height, filename);
if(error){
printf("error %u: %s\n", error, lodepng_error_text(error));
}
Arraysize = 4*height*width;
printf("arraysize:%d, %d \n",sizeof(image)/sizeof(image[0]),Arraysize);
pthread_mutex_unlock(&mutex);
return NULL; }
void *threadFunc2(void *arg){ //function to apply inverse process on loaded data
struct Markers*vars= (struct Markers*) arg;
printf("Thread %d start|start-%d,end-%d\n",vars->no,vars->start,vars->end);
for( int i = vars->start; i<(vars->end); i=i+4){
pthread_mutex_lock(&mutex);
image[0+i]= 255-image[0+i];
image[1+i]= 255-image[1+i];
image[2+i]= 255-image[2+i];
image[3+i]= 255-image[3+i];// can be ignored
printf("Thread: %d,round:%d\n",vars->no,i);// debug line
pthread_mutex_unlock(&mutex);
}
// printf("Thread: %d,after end:%d\n",vars->no,i);
printf("**************************\n");
printf("Thread end\n");
return NULL;
}
void *encodeprocessed(){
encError = lodepng_encode32_file(newFileName, image, width, height);
if(encError){
printf("error %u: %s\n", error, lodepng_error_text(encError)); }
free(image); }
Following is the main function
int main(void){
pthread_t pth,pth0, pth1, pth2, pth3;
pthread_create(&pth,NULL,threadFunc,NULL);
pthread_join(pth, NULL );
struct Markers Positions[3];
Positions[0].start = 0;
Positions[0].end = Arraysize/4 -1;
Positions[0].no = 1;
Positions[1].start =Arraysize/4;
Positions[1].end = Arraysize/2 -1;
Positions[1].no = 2;
Positions[2].start =Arraysize/2;
Positions[2].end = Arraysize*3/4 -1;
Positions[2].no = 3;
Positions[3].start =(Arraysize*3)/4;
Positions[3].end = Arraysize -1;
Positions[3].no = 4;
//debug line
printf("%d,%d,%d,%d,%d,%d,%d,%d,%d\n",Arraysize,Positions[0].start,Positions[0].end,
Positions[1].start,Positions[1].end,Positions[2].start,Positions[2].end,
Positions[3].start,Positions[3].end);
pthread_create(&pth0,NULL,threadFunc2,&Positions[0]);
pthread_create(&pth1,NULL,threadFunc2,&Positions[1]);
pthread_create(&pth2,NULL,threadFunc2,&Positions[2]);
pthread_create(&pth3,NULL,threadFunc2,&Positions[3]);
pthread_join(pth0, NULL );
pthread_join(pth1, NULL );
pthread_join(pth2, NULL );
pthread_join(pth3, NULL );
encodeprocessed();
return 0;}
The code is running without any errors. Image gets inverted only 75% and gets saved. Anyone who can give me a clue is much appreciated.
In the code below I wrote a program to perform add/remove operations on an int array using multithreading. The condition is that multiple threads cannot make operations on the same cell, but parallel operations can be made on different cells.
I thought in order to implement such conditions I'd need to use multiple mutexes and condition variables, to be exact, as many as there're cells in the array. The initial value of all cells of my array is 10 and threads increment/decrement this value by 3.
The code below seems to work (the cell values of the array after all threads finished working is as expected) but I don't understand a few things:
I first spawn adder threads which sleep for a second. In addition each thread has printf statement which is triggered if a thread waits. Remove threads don't sleep so I expect remove threads to invoke their printf statements because they must wait a second at least before adder threads finish their work. But remover threads never call printf.
My second concern: as I mentioned I first spawn adder threads so I expect the cells value go from 10 to 13. Then if remover thread acquires lock the value can go from 13 to 10 OR if adder thread acquires the lock then the cell value will go from 13 to 16. But I don't see the behavior in printf statements inside threads. For example one of the printf sequences I had: add thread id and cell id 1: cell value 10->13, then remove thread id and cell id 1: cell value 10->7 then add thread id and cell id 1: cell value 10->13. This doesn't make sense. I made sure that the threads all point to the same array.
Bottom line I'd like to know whether my solution is correct and if yes why is the behavior I described occurring. If my solution is incorrect I'd appreciate example of correct solution or at least general direction.
This is the code (all the logic is in AdderThread, RemoveThread):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define ARR_LEN 5
#define THREADS_NUM 5
#define INIT_VAL 10
#define ADD_VAL 3
#define REMOVE_VAL 3
#define ADDER_LOOPS 2
typedef struct helper_t {
int threadId;
int * arr;
int * stateArr; //0 if free, 1 if busy
} helper_t;
enum STATE {FREE, BUSY};
enum ERRORS {MUTEX, COND, CREATE, JOIN, LOCK, UNLOCK, WAIT, BROADCAST};
pthread_mutex_t mutexArr[THREADS_NUM];
pthread_cond_t condArr[THREADS_NUM];
void errorHandler(int errorId) {
switch (errorId) {
case MUTEX:
printf("mutex error\n");
break;
case COND:
printf("cond error\n");
break;
case CREATE:
printf("create error\n");
break;
case JOIN:
printf("join error\n");
break;
case LOCK:
printf("lock error\n");
break;
case UNLOCK:
printf("unlock error\n");
break;
case WAIT:
printf("wait error\n");
break;
case BROADCAST:
printf("broadcast error\n");
break;
default:
printf("default switch\n");
break;
}
}
void mallocError() {
printf("malloc error\nExiting app\n");
exit(EXIT_FAILURE);
}
void initMutexesAndConds(pthread_mutex_t * mutexArr, pthread_cond_t * condArr) {
int i;
for(i = 0; i < THREADS_NUM; i++) {
pthread_mutex_init(&mutexArr[i], NULL);
pthread_cond_init(&condArr[i], NULL);
}
}
helper_t * initStructs(int * arr, int * stateArr) {
int i;
helper_t * helpers = (helper_t *) malloc(sizeof(helper_t) * THREADS_NUM);
if(!helpers) {
mallocError();
} else {
for(i = 0; i < THREADS_NUM; i++) {
helpers[i].threadId = i;
helpers[i].arr = arr;
helpers[i].stateArr = stateArr;
}
}
return helpers;
}
void printArr(int * arr, int len) {
int i;
for(i = 0; i < len; i++) {
printf("%d, ", arr[i]);
}
printf("\n");
}
void * AdderThread(void * arg) {
int i;
helper_t * h = (helper_t *) arg;
int id = h->threadId;
for(i = 0; i < ADDER_LOOPS; i++) {
pthread_mutex_t * mutex = &mutexArr[id];
pthread_cond_t * cond = &condArr[id];
if(pthread_mutex_lock(mutex)) {
errorHandler(LOCK);
}
while(h->stateArr[id] == BUSY) {
printf("adder id %d waiting...\n", id);
if(pthread_cond_wait(cond, mutex)) {
errorHandler(WAIT);
}
}
h->stateArr[id] = BUSY;
sleep(1);
h->arr[id] = h->arr[id] + ADD_VAL;
printf("add thread id and cell id %d: cell value %d->%d\n", id, h->arr[id]-ADD_VAL, h->arr[id]);
h->stateArr[id] = FREE;
if(pthread_cond_broadcast(cond)) {
errorHandler(BROADCAST);
}
if(pthread_mutex_unlock(mutex)) {
errorHandler(UNLOCK);
}
}
pthread_exit(NULL);
}
void * RemoveThread(void * arg) {
helper_t * h = (helper_t *) arg;
int id = h->threadId;
pthread_mutex_t * mutex = &mutexArr[id];
pthread_cond_t * cond = &condArr[id];
if(pthread_mutex_lock(mutex)) {
errorHandler(LOCK);
}
while(h->stateArr[id] == BUSY) {
printf("remover id %d waiting...\n", id);
if(pthread_cond_wait(cond, mutex)) {
errorHandler(WAIT);
}
}
h->stateArr[id] = BUSY;
h->arr[id] = h->arr[id] - REMOVE_VAL;
printf("remove thread id and cell id %d: cell value %d->%d\n", id, h->arr[id], h->arr[id]-ADD_VAL);
h->stateArr[id] = FREE;
if(pthread_cond_broadcast(cond)) {
errorHandler(BROADCAST);
}
if(pthread_mutex_unlock(mutex)) {
errorHandler(UNLOCK);
}
pthread_exit(NULL);
}
int main() {
int i;
helper_t * adderHelpers;
helper_t * removeHelpers;
pthread_t adders[THREADS_NUM];
pthread_t removers[THREADS_NUM];
int * arr = (int *) malloc(sizeof(int) * ARR_LEN);
int * stateArr = (int *) malloc(sizeof(int) * ARR_LEN);
if(!arr || !stateArr) {
mallocError();
}
for(i = 0; i < ARR_LEN; i++) {
arr[i] = INIT_VAL;
stateArr[i] = FREE;
}
initMutexesAndConds(mutexArr, condArr);
adderHelpers = initStructs(arr, stateArr);
removeHelpers = initStructs(arr, stateArr);
for(i = 0; i < THREADS_NUM; i++) {
pthread_create(&adders[i], NULL, AdderThread, &adderHelpers[i]);
pthread_create(&removers[i], NULL, RemoveThread, &removeHelpers[i]);
}
for(i = 0; i < THREADS_NUM; i++) {
pthread_join(adders[i], NULL);
pthread_join(removers[i], NULL);
}
printf("the results are:\n");
printArr(arr, THREADS_NUM);
printf("DONE.\n");
return 0;
}
1) This code sequence in Addr:
h->stateArr[id] = BUSY;
sleep(1);
h->arr[id] = h->arr[id] + ADD_VAL;
printf("add thread id and cell id %d: cell value %d->%d\n", id, h->arr[id]-ADD_VAL, h->arr[id]);
h->stateArr[id] = FREE;
Is execute with the mutex locked; thus Remove would never get a chance to see the state as anything but FREE.
2) There is no guarantee that mutex ownership alternates (afaik), but at the very least, to properly co-ordinate threads you should never rely upon such an implementation detail. It is the difference between working and “happens to work”, which usually leads to “used to work”....
If you put the sleep() between the mutex unlock and mutex lock, you might have a better case, but as it is, it just unlocks it then locks it again, so the system is well within its rights to just let it continue executing.
[ I ran out of space in comments ... ]:
Yes, the condition variables are doing nothing for you here. The idea of a condition variable is to be able to be notified when a significant event, such as a state change, has occurred on some shared objection.
For example, a reservoir might have a single condition variable for the water level. Multiplexed onto that might be many conditions: level < 1m; level > 5m; level > 10m. To keep the systems independent (thus working), the bit that updates the level might just:
pthread_mutex_lock(&levellock);
level = x;
pthread_cond_broadcast(&newlevel);
pthread_mutex_unlock(&levellock);
The actors implementing the conditions would do something like:
pthread_mutex_lock(&levellock);
while (1) {
if (level is my conditions) {
pthread_mutex_unlock(&levellock);
alert the media
pthread_mutex_lock(&levellock);
}
pthread_cond_wait(&newlevel, &levellock);
}
Thus I can add many “condition monitors” without breaking the level setting code, or the overall system. Many is finite, but by releasing the mutex while I alert the media, I avoid having my water monitoring system rely on the alarm handling.
If you are familiar with “publish/subscribe”, you might find this familiar. This is fundamentally the same model, just the PS hides a pile of details.
I'm modeling a train station with pthreads. Each train gets its own thread and its own condition variable that monitors its access to the main track. Train information is read from a file and is of the format:
(direction):(loading time):(crossing time)
Only one train can be on the main track at a time. Trains can't be on the main track unless they are loaded and ready.
There is a seperate dispatcher thread responsible for coordinating access to the main track between all the trains. The dispatcher thread also decides which train gets access based on various rules such as which direction the train is going in. Right now I would be happy if I could get the trains reaching the main track in the order they are ready.
Here is my code so far:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <readline/readline.h>
#include <unistd.h>
struct Train{
pthread_t thread;
pthread_cond_t granted;
int train_number;
int loading_time;
int crossing_time;
int priority;
char direction;
char state;
}*newTrain;
struct Train *trains[3];
pthread_mutex_t track = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t dispatcher = PTHREAD_COND_INITIALIZER;
char *loading = "L";
char *ready = "R";
char *granted_t = "T";
char *gone = "G";
char *acknowledged_gone = "X";
void *dispatcher_function(void *train_count) {
int count = *((int *) train_count);
int trains_remaining = count;
/* Check for ready trains until all trains have left the station */
while (trains_remaining > 0) {
pthread_mutex_lock(&track);
int t_granted = 0;
int next = -1;
for(int i = 0; i < count; i++){
if (strcmp(&trains[i]->state, "T") == 0)
t_granted = 1;
if (strcmp(&trains[i]->state, "R") == 0)
next = i;
if (strcmp(&trains[i]->state, "G") == 0){
trains_remaining--;
trains[i]->state = *acknowledged_gone;
}
}
/* Train was granted access to station wait for it to leave */
if (t_granted) {
pthread_cond_wait(&dispatcher, &track);
}
/* No trains in station. Wait for train */
if (next == -1) {
pthread_cond_wait(&dispatcher, &track);
}
/* Train ready in station grant next train track permission*/
else{
trains[next] -> state = *granted_t;
pthread_cond_signal(&(trains[next] -> granted));
}
pthread_mutex_unlock(&track);
}
pthread_exit(0);
}
void *train_function(void* train) {
struct Train *self = (struct Train*)train;
/* Each train has its own cond var */
pthread_cond_init(&self->granted, NULL);
/* Load train */
usleep(self -> loading_time);
/* Lock track */
pthread_mutex_lock(&track);
/* Train ready */
self -> state = *ready;
printf("Train %d is ready to go %c\n", self -> train_number, self -> direction);
/* Signal dispatcher */
pthread_cond_signal(&dispatcher);
while(strcmp(&self->state, "T") != 0)
pthread_cond_wait(&(self->granted), &track);
/* Use the track */
printf("Train %d is ON the main track going %c\n", self -> train_number, self -> direction);
usleep(self -> crossing_time);
self -> state = *gone;
printf("Train %d is OFF the main track after going %c\n", self -> train_number, self -> direction);
pthread_cond_signal(&dispatcher);
pthread_mutex_unlock(&track);
pthread_exit(0);
}
int main() {
FILE *ptr_file;
char buff[10];
int train_count = 0;
char *train;
char line[15];
pthread_t train_threads[3];
pthread_t dispatcher_thread;
ptr_file = fopen("./trains.txt", "r");
if (!ptr_file)
{
perror("fopen for trains.txt failed");
exit(EXIT_FAILURE);
}
/* Create train for each line of file */
while (fgets(buff,10, ptr_file)!=NULL) {
train = (char*)malloc(10 * sizeof(char));
/* Build train string */
sprintf(line, "%d:", train_count);
strcat(line, buff);
strcpy(train, line);
/* Parse train information */
int line_number = atoi(strtok(train, ":,"));
char *direction = strtok(NULL,":,");
int loading_time = atoi(strtok(NULL, ":,"));
int crossing_time = atoi(strtok(NULL, ":,"));
/* Create trains */
newTrain = (struct Train *) malloc(sizeof(struct Train));
newTrain -> train_number = line_number;
newTrain -> crossing_time = crossing_time;
newTrain -> loading_time = loading_time;
newTrain -> direction = *direction;
newTrain -> state = *loading;
if(pthread_create(&train_threads[train_count], NULL, &train_function, (void *) newTrain))
{
perror("pthread create failed");
exit(EXIT_FAILURE);
}
trains[line_number] = newTrain;
train_count++;
}
fclose(ptr_file);
/* Create dispatcher */
if(pthread_create(&dispatcher_thread, NULL, &dispatcher_function, (void *) &train_count))
{
perror("pthread create failed");
exit(EXIT_FAILURE);
}
/* Wait for dispatcher to finish */
pthread_join(dispatcher_thread, NULL);
printf("all done");
free(train);
for (int i = 0; i < train_count; i++) {
free(trains[i]);
}
exit(EXIT_SUCCESS);
}
Here is the trains.txt input file:
e:10,6
W:5,7
E:3,10
And this is the output I get when I run it:
Train 0 is ready to go e
Train 0 is ON the main track going e
Train 0 is OFF the main track after going e
Train 2 is ready to go E
Train 1 is ready to go W
Train 2 is ON the main track going E
Train 2 is OFF the main track after going E
Train 1 is ON the main track going W
Train 1 is OFF the main track after going W
The program is now hanging after all trains have left the station. So close I must be missing something.
You have several errors that need to be corrected before your program can be reasoned about:
trains.state is a single char object - it's not a string, because it's not necessarily followed by a null terminator. This means that you can't pass the address of it to strcmp() like you do in several places - instead of this:
if (strcmp(&trains[i]->state, "T") == 0)
use:
if (trains[i]->state == 'T')
(note single quotes for a character constant instead of a string constant).
You can't free(train) at the end of the train_function(), because the dispatcher keeps running and needs to access all the train structures. Instead, free them all in main() after the dispatcher exits.
The train condition variable granted is not ever initialised. You can't copy around pthread_cond_t variables - instead use pthread_cond_init:
void *train_function(void* train)
{
struct Train *self = (struct Train*)train;
/* Each train has its own cond var */
pthread_cond_init(&self->granted, NULL);
The start of train_function() modifies self->state without holding the lock, and this means it can race with the dispatcher reading the same object. You need to hold the lock around the modifications:
/* Load train */
pthread_mutex_lock(&track);
self -> state = *loading;
pthread_mutex_unlock(&track);
usleep(self -> loading_time);
/* Lock track */
pthread_mutex_lock(&track);
/* Train ready */
self -> state = *ready;
printf("Train %d is ready to go %c\n", self -> train_number, self -> direction);
(you can avoid the first lock by initialising state to "loading" for all trains in main(), before the other threads are started).
In addition, you cannot assume that the condition you were waiting for is true just because pthread_cond_wait() has woken up. pthread_cond_wait() is allowed to return even if it hasn't been signalled; it returning just means that it might have been signalled. This means that, for example in the train_function, you need to loop around the pthread_cond_wait() using while instead of if:
while (self->state != 'T')
pthread_cond_wait(&self->granted, &track);
You need to do something similar for the case in the dispatcher where you find that there's a train on the track.
This is actually the heart of your problem - when you get woken up at this point:
if (t_granted) {
pthread_cond_wait(&dispatcher, &track);
trains_remaining--;
}
You assume it's because the train you saw on the tracks has now finished. But that's not necessarily true - you might have been signalled by the next train finishing loading. This means you'll go around the loop and see the same train on the tracks again, and decrement trains_remaining too many times.
So you can't just adjust trains_remaining when you see a train on the track, because you might see the same train twice - or not at all, if it sets itself to "gone" very quickly.
Instead, you should decrement trains_remaining the first time that you see a given train in the "gone" state. You could achieve this by adding a new state that the dispatcher sets the train to after it has seen it was "gone", something like:
for (int i = 0; i < count; i++) {
if (trains[i]->state == 'T')
t_granted = 1;
if (trains[i]->state == 'R')
next = i;
if (trains[i]->state == 'G') {
trains_remaining--;
trains[i]->state = 'X'; /* acknowledged gone */
}
}
I am new to multithreading, and any answers will be greatly appreciated. I am running an example from a tutorial which uses 3 threads; two created by the user, and one for main itself. Here's the code:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define NUM_EMPLOYEES 2
/* global mutex for our program. assignment initializes it */
pthread_mutex_t a_mutex = PTHREAD_MUTEX_INITIALIZER;
struct employee {
int number;
int id;
char first_name[20];
char last_name[30];
char department[30];
int room_number;
};
/* global variable - our employees array, with 2 employees */
struct employee employees[] = {
{1, 12345678, "danny", "cohen", "Accounting", 101},
{2, 87654321, "moshe", "levy", "Programmers", 202}
};
/* global variable - employee of the day */
struct employee employee_of_the_day;
void copy_employee(struct employee *from, struct employee *to) {
int rc; /* contain mutex lock/unlock results */
/*lock the mutex, to assure exclusive access to 'a' and 'b' */
rc = pthread_mutex_lock(&a_mutex);
to->number = from->number;
to->id = from->id;
strcpy(to->first_name, from->first_name);
strcpy(to->last_name, from->last_name);
strcpy(to->department, from->department);
to->room_number = from->room_number;
/* unlock mutex */
rc = pthread_mutex_unlock(&a_mutex);
}
/* function to be executed by the variable setting threads thread */
void *do_loop(void *data) {
int my_num = *((int*)data);
while(1) {
/* set employee of the day to be the one with number 'my_num' */
copy_employee(&employees[my_num-1], &employee_of_the_day);
}
}
/* program's execution begins in main */
int main(int argc, char *argv[]) {
int i;
int thr_id1;
int thr_id2;
pthread_t p_thread1;
pthread_t p_thread2;
int num1 = 1;
int num2 = 2;
struct employee eotd;
struct employee *worker;
/* initialize employee of the day to first 1 */
copy_employee(&employees[0], &employee_of_the_day);
/* create a new thread that will execute 'do_loop()' with '1' */
thr_id1 = pthread_create(&p_thread1, NULL, do_loop, (void*)&num1);
/* create a new thread that will execute 'do_loop()' with '2' */
thr_id2 = pthread_create(&p_thread2, NULL, do_loop, (void*)&num2);
/* run a loop that verifies integrity of 'employee of the day' many */
/* many times.... */
for (i = 0; i < 600000; i++) {
/* save contents of 'employee of the day' to local 'worker' */
copy_employee(&employee_of_the_day, &eotd);
worker = &employees[eotd.number-1];
/* compare employees */
if (eotd.id != worker->id) {
printf("mismatching 'id', %d != %d (loop '%d')\n",
eotd.id, worker->id, i);
exit(0);
}
if (strcmp(eotd.first_name, worker->first_name) != 0) {
printf("mismatching 'first_name' , %s != %s (loop '%d')\n",
eotd.first_name, worker->first_name, i);
exit(0);
}
if (strcmp(eotd.last_name, worker->last_name) != 0) {
printf("mismatching 'last_name' , %s != %s (loop '%d')\n",
eotd.last_name, worker->last_name, i);
exit(0);
}
if (strcmp(eotd.department, worker->department) != 0) {
printf("mismatching 'department' , %s != %s (loop '%d')\n",
eotd.department, worker->department, i);
exit(0);
}
if (eotd.room_number != worker->room_number) {
printf("mismatching 'room_number' , %d != %d (loop '%d')\n",
eotd.room_number, worker->room_number, i);
exit(0);
}
}
printf("Glory, employees contents was always consistent\n");
return 0;
}
I basically want to confirm that in the for loop in main, the following statement
copy_employee(&employee_of_the_day, &eotd);
could be executed by ANY of the 3 threads; am I right?
The fact that the subsequent comparisons are obviously not atomic raises some confusions. Any clarifications/corrections to this will be greatly helpful.
Incidentally, any good recommendations for tutorials on multithreading in C?
Thanks a lot!
No, the code in main is executed by only one thread.
The atomicity is ensured in copy_employee functions using mutexes.
Your main thread (and none of the worker threads) will execute everything within main() and then end. Both of your worker threads will execute everything within do_loop() and end once they leave the function.
This sounds a bit like you're confusing phtread_create() with fork(). pthread_create() will use the function provided as the entry point while fork() will start from the position it's been called.
I basically want to confirm that in the for loop in main, the following statement
copy_employee(&employee_of_the_day, &eotd);
could be executed by ANY of the 3 threads; am I right?
Not really, since that statement is only executed by the main thread and not by the other two threads.