This code seems to work. The primary significant change is to add pthread_cond_signal()
on the the other thread's condition before going into the while (who == N)
loops.
Other changes include basic debug printing to make it easier to see what's going on an which thread is doing what. Note that debugging messages end with newlines.
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern void *getMessage1(void *arg);
extern void *getMessage2(void *arg);
static char message[4096];
int main(void)
{
pthread_t id1;
pthread_t id2;
pthread_create((&id1), NULL, getMessage1, NULL);
pthread_create((&id2), NULL, getMessage2, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
for (int j = 0; j < 1001 && message[j] != '\0'; j++)
printf("%c ", message[j]);
putchar('\n');
return 0;
}
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t condition1 = PTHREAD_COND_INITIALIZER;
static pthread_cond_t condition2 = PTHREAD_COND_INITIALIZER;
static int who = 1;
void *getMessage1(void *arg)
{
assert(arg == NULL);
const char filename[] = "Student1";
FILE *studentOne = fopen(filename, "r");
if (studentOne == NULL)
{
fprintf(stderr, "Failed to open file %s for reading\n", filename);
exit(EXIT_FAILURE);
}
size_t howManyChars;
char *placeHolderChars;
int count = 1;
while (count < 501)
{
placeHolderChars = NULL;
if (getline(&placeHolderChars, &howManyChars, studentOne) == -1)
break;
printf("M1(%d): [%s]\n", count, placeHolderChars);
pthread_mutex_lock(&lock);
if (strcmp(placeHolderChars, "0\n") == 0)
{
printf("M1: Two's turn - 1\n");
pthread_cond_signal(&condition2);
who = 2;
while (who == 2)
{
pthread_cond_wait(&condition1, &lock);
}
free(placeHolderChars);
}
else
{
if (who == 1)
{
if (strlen(placeHolderChars) > 0)
{
placeHolderChars[1] = '\0';
}
strcat(message, placeHolderChars);
free(placeHolderChars);
who = 2;
pthread_cond_signal(&condition2);
}
else
printf("M1: Two's turn - 2\n");
}
pthread_mutex_unlock(&lock);
count++;
}
fclose(studentOne);
return 0;
}
void *getMessage2(void *arg)
{
assert(arg == NULL);
const char filename[] = "Student2";
FILE *studentTwo = fopen(filename, "r");
if (studentTwo == NULL)
{
fprintf(stderr, "Failed to open file %s for reading\n", filename);
exit(EXIT_FAILURE);
}
size_t howManyChars;
char *placeHolderChars;
int count = 0;
while (count < 501)
{
placeHolderChars = NULL;
if (getline(&placeHolderChars, &howManyChars, studentTwo) == -1)
break;
printf("M2(%d): [%s]\n", count, placeHolderChars);
pthread_mutex_lock(&lock);
if (strcmp(placeHolderChars, "0\n") == 0)
{
printf("M2: One's turn - 1\n");
pthread_cond_signal(&condition1);
who = 1;
while (who == 1)
{
pthread_cond_wait(&condition2, &lock);
}
free(placeHolderChars);
}
else
{
if (who == 2)
{
if (strlen(placeHolderChars) > 0)
{
placeHolderChars[1] = '\0';
}
strcat(message, placeHolderChars);
free(placeHolderChars);
who = 1;
pthread_cond_signal(&condition1);
}
else
printf("M2: One's turn - 2\n");
}
pthread_mutex_unlock(&lock);
count++;
}
fclose(studentTwo);
return 0;
}
You should be able to refine the code such that you pass a structure containing the relevant per-thread data (file name, current thread condition, other thread condition, maybe a 'thread ID') to a single function, so you have just getMessage()
.
Output:
M1(1): [h
]
M2(0): [0
]
M1(2): [0
]
M2: One's turn - 1
M1: Two's turn - 1
M2(1): [i
]
M2(2): [0
]
M2: One's turn - 1
M1(3): [h
]
M1(4): [0
]
M1: Two's turn - 1
M2(3): [i
]
M2(4): [0
]
M2: One's turn - 1
M1(5): [h
]
M1(6): [0
]
M1: Two's turn - 1
M2(5): [i
]
M2(6): [0
]
M2: One's turn - 1
M1(7): [h
]
M1(8): [0
]
M1: Two's turn - 1
M2(7): [i
]
M2(8): [0
]
M2: One's turn - 1
M1(9): [h
]
M1(10): [0
]
M1: Two's turn - 1
M2(9): [i
]
h i h i h i h i h i
I'm not completely happy with this code. I created a modified version with a single function used by both threads, as I hinted should be done, and modified the printing of the lines read to avoid printing the newlines (making the output more compact). Sometimes — not all the time — it would deadlock at the end. Two sample traces, one working, one deadlocking (program name pth47
):
$ pth47
M2(1): [0]
M2: 1's turn - 1
M1(1): [h]
M1(2): [0]
M1: 2's turn - 1
M2(2): [i]
M2(3): [0]
M2: 1's turn - 1
M1(3): [h]
M1(4): [0]
M1: 2's turn - 1
M2(4): [i]
M2(5): [0]
M2: 1's turn - 1
M1(5): [h]
M1(6): [0]
M1: 2's turn - 1
M2(6): [i]
M2(7): [0]
M2: 1's turn - 1
M1(7): [h]
M1(8): [0]
M1: 2's turn - 1
M2(8): [i]
M2(9): [0]
M2: 1's turn - 1
M1(9): [h]
M1(10): [0]
M1: 2's turn - 1
M2(10): [i]
h i h i h i h i h i
$ pth47
M1(1): [h]
M2(1): [0]
M1(2): [0]
M2: 1's turn - 1
M1: 2's turn - 1
M2(2): [i]
M2(3): [0]
M2: 1's turn - 1
M1(3): [h]
M1(4): [0]
M1: 2's turn - 1
M2(4): [i]
M2(5): [0]
M2: 1's turn - 1
M1(5): [h]
M1(6): [0]
M1: 2's turn - 1
M2(6): [i]
M1(7): [h]
M1(8): [0]
M2(7): [0]
M1: 2's turn - 1
M2: 1's turn - 1
M1(9): [h]
M1(10): [0]
M2(8): [i]
M1: 2's turn - 1
M2(9): [0]
M2: 1's turn - 1
^C
$
I've not tracked down the aberration. It isn't as simple as 'thread one went first'; there are examples where thread one went first and it completed fine.