0

Edited as per commenter's request.

This program creates two threads. Each thread reads from one of two specific input files, each of which contains either one letter or one '0' per line of code. The threads are supposed to read the letters into a global char array, which is then printed. The problem is that, upon reaching a '0,' the active thread must transfer control to the other thread, which should not have a '0' on that line. (We are sure that, if File 1 has a '0' on a line, then File 2, on the corresponding line, has a letter. Multiple zeros can follow one another, as can multiple letters.)

FILE ONE

h
0
h
0
h
0
h
0
h
0

FILE TWO

0
i
0
i
0
i
0
i
0
i

I am attempting to use pthread mutex lock/unlock as well as signal and wait to make this work. However, I keep reaching a state of deadlock.

There are two threads. Currently, they mirror each other meaning that they do the same things, just with different files and opposite conditions.

Thread Example:

char final[1001];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condition1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t condition2 = PTHREAD_COND_INITIALIZER;

int w = 1;

void *get()
{
//start reading
while (count < //number)
{
    pthread_mutex_lock(&lock);

    //read line

    //if we've reached a zero
    {
        w = 2;             

         while(w == 2)
         {
            pthread_cond_wait(&condition1, &lock);
         }


         pthread_mutex_unlock(&lock);
    }
    else
    {   
       if(w == 1)
       {

            if(strlen(placeHolderChars)>0)
            {
                 placeHolderChars[1] = '\0';
            }

            //copy char to array

            w= 2;
            pthread_cond_signal(&condition2);
            pthread_mutex_unlock(&lock);
        }
    }

    if(feof(file))
    {     
        fclose(file);
        break;
    }
    count++;

 }

return 0;
}

UPDATE: Signal-before-wait strategy did not really work when using a larger file. Still working on this!

Don
  • 35
  • 1
  • 8
  • 1
    Besides beign uncompilable as-shown, your never lock the mutex in `getMessage1` prior to entering your file loop. You *must* own the mutex before invoking `pthread_cond_wait`. Second, and more importantly, `who` should *never* be modified, nor even *examined*, unless under the protection of the mutex, which is the whole reason for its existence in the first place. The mutex protects the *predicate data*, which in your case, is `who`. [Read this](https://stackoverflow.com/questions/14924469/does-pthread-cond-waitcond-t-mutex-unlock-and-then-lock-the-mutex/14925150#14925150). – WhozCraig Sep 24 '17 at 04:58
  • thank you both so much for your help. two quick questions: (1) would this work with only one condition variable? (2) if I modify the code so that the predicate is only examined/altered under a mutex lock, would the predicate that I've chosen work? or should I create a different predicate variable? – Don Sep 24 '17 at 05:17
  • I added my updated code (which still doesn't work). I tried to follow along with the instructions in your (very helpful) guide, but obviously I'm still doing something wrong. I'm going to try to look back at the guide + modify my code, and, as always, I'd appreciate any suggestions. – Don Sep 24 '17 at 05:28
  • I updated my code a second time -- still getting deadlock. I changed it so that there is only one condition. I wonder if I should change it back. – Don Sep 24 '17 at 05:52
  • We can't see how you're using this code. Please create an MCVE ([MCVE]) that reproduces the trouble — as that will allow us to try and find the problem. Without an MCVE, we can only guess at how you're creating threads, how many threads you're creating, and so on. And we may not be able to guess accurately what you've done. – Jonathan Leffler Sep 24 '17 at 05:57
  • Thanks for your response. I tried my best to better demonstrate how I'm using the code in this edit. – Don Sep 24 '17 at 06:12
  • Are the functions `getMessage1()` and `getMessage2()` the ones passed to `pthread_create()`? If so, they don't match the expected signature of a thread function — they should take a `void *` argument, even if they ignore it. Oh, yes, they are the thread functions. Ugh! Well, I'll have to fix that before I can compile the code; the default options I use won't allow such sloppy programming. You should check the return value from `getline()` to detect EOF properly. You certainly shouldn't go comparing the string unless you know `getline()` read one. – Jonathan Leffler Sep 24 '17 at 06:20
  • You usually want to keep the mutex locked for as short a time as possible. Since the `getline()` call uses only local variables, not a global variable, you shouldn't have the mutex locked while you're reading. You don't show the definition of the global variable `message`. – Jonathan Leffler Sep 24 '17 at 06:21
  • yes, I've also tried doing it so that the lock and unlock statements are on the edges of the if clause and the else clause. is that how I should do it? – Don Sep 24 '17 at 06:27
  • It would be helpful to have minimal examples of each of the two files. Treat their contents like code (select and use the **`{}`** button above the edit box to indent it. If you're feeling fancy, add a line `` above the file data as a left-aligned line with a blank line before and after it. – Jonathan Leffler Sep 24 '17 at 06:27
  • I've added a 10-line sample of each file. The correct output (using these files) would be 'hihihihihi.' – Don Sep 24 '17 at 06:35
  • Thanks for the data files. Does the program deadlock on those files? How have you gone about debugging this code? Where have you printing statements so you know what's going on, and which thread is stuck where? – Jonathan Leffler Sep 24 '17 at 06:38
  • In earlier versions, I wasn't getting deadlock, but I was getting incorrect output, so I was just trying different mutex/conditional techniques. Now, I'm actually unsure of how to do (print statement checking) with threads. (This is my first time working with them.) I attempted to put some print statements in each thread's critical section; however, nothing printed. – Don Sep 24 '17 at 06:41
  • Yes, the program deadlocks on the 500-line version of those files. – Don Sep 24 '17 at 06:41
  • Oh, and I was using gdb, earlier when my code was segfaulting. – Don Sep 24 '17 at 06:43
  • I just used gdb now, and I received this information: Thread 1 "a.out" received signal SIGINT, Interrupt. 0x00007ffff7bc298d in pthread_join (threadid=140737345685248, thread_return=0x0) at pthread_join.c:90 90 pthread_join.c: No such file or directory. – Don Sep 24 '17 at 06:44
  • Possible duplicate of [Pthread Synchronization: Back & Forth Reading of Two Text Files](https://stackoverflow.com/questions/46384349/pthread-synchronization-back-forth-reading-of-two-text-files) – Erik Alapää Sep 25 '17 at 15:46

2 Answers2

0

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thank you so much! I'm so confused -- I thought that it was not 'correct' to place a signal before a wait. But if this works it makes so much sense. I no longer have access to a computer, but I'll probably try to implement the signal-before-loops tomorrow morning, so I'll update then. Again, thank you so much for your help/time. – Don Sep 24 '17 at 07:06
  • yeah, I still get deadlock. However, I think what helped me the most about your answer was (1) allowing me to realize that I could use signal before wait, and (2) giving me an idea of how to use print statements to debug a thread. so thank you. i'll continue playing with this & see what happens. – Don Sep 24 '17 at 18:23
0

I think the person who posed the question did it twice, a bit annoying. FWIW, here is my answer to the duplicate: Pthread Synchronization: Back & Forth Reading of Two Text Files

Erik Alapää
  • 2,585
  • 1
  • 14
  • 25