LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 03-03-2012, 09:29 PM   #1
badmofo666
Member
 
Registered: Mar 2004
Location: Texas, USA
Distribution: Ubuntu 12 (notebook), Debian Squeeze (home server), OpenSuse 12 (desktop)
Posts: 96

Rep: Reputation: 15
Trouble understanding thread synchronization.


I'm trying to use the pthread library to accomplish something simple, but can't get it to work. I'm guessing I'm not understanding something correctly. Here's the situation:

I've got two threads, the main thread, and one I create. Thread 1 has two actions that must be atomic, A and C. Thread 2 has one action that must be atomic, B. These are continuously looped over, and should be executed in the order A->B->C. Here's what I got, but results in the program just freezing:

Thread 1:
while(1) {
A
pthread_cond_signal(&aDone);
pthread_mutex_unlock(&mutex);

pthread_mutex_lock(&mutex);
pthread_cond_wait(&bDone);
C
}

Thread 2:
while(1) {
pthread_mutex_lock(&mutex);
pthread_cond_wait(&aDone);
B
pthread_cond_signal(&bDone);
pthread_mutex_unlock(&mutex);
}

What's wrong with this?
 
Old 03-03-2012, 10:31 PM   #2
Four
Member
 
Registered: Aug 2005
Posts: 298

Rep: Reputation: 30
What you trying to do is solved with consumer producer paradigm.

To get what you need to work, you need some form of "state" variable. I put 'state' in quotes cause consumer-producer is not modeled with states exactly.


Code:
// each thread locks the mutex here

while(1){
lock(&mutex)
// this thread owns mutex at this point
if(state == STATE_A || state == STATE_C){
  performState(state);
  signal(&cond)
  // you still own mutex
  unlock(&mutex)
} else {
  // pthread wait unlocks the mutex, and when it the thread
  // starts up again you regain the lock
  wait(&cond, &mutex)
}


}
Similar idea for other thread. Essentially after the lock, you check if the thread has work to do, if not you do wait() until the proper state is met for the thread. The reason you get a deadlock is because after the lock, you immediately wait in thread 2. Say thread 2 locks, and its time to do B, next thread 2 wait(), now thread 2 will never do B, as the other thread is waiting for B to get done.

Its a bit confusing, to me to I might have mistakes there.
 
Old 03-04-2012, 09:18 PM   #3
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
I do believe POSIX.1-2001 semaphores fit this use case better:
Code:
    sem_wait(&runA);
    /* Do task A */
    sem_post(&runB);

    sem_wait(&runB);
    /* Do task B */
    sem_post(&runC);

    sem_wait(&runC);
    /* Do task C */
    sem_post(&runA);
It does not matter which thread does which task, as long as it waits on the semaphore belonging to the task first, then posts the semaphore belonging to the next task.

Initialize the semaphores using sem_init(&runX,0,0), and after creating all the worker tasks, start them working by posting the first task semaphore, i.e. sem_post(&runA);

Here is tested example code in C99:
Code:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>

sem_t   runA;
sem_t   runB;
sem_t   runC;

void *worker1(void *payload)
{
    const long count = (long)payload;
    long       i;

    for (i = 1L; i <= count; i++) {

        sem_wait(&runA);
        fprintf(stdout, "Task A, round %ld of %ld\n", i, count); fflush(stdout);
        sem_post(&runB);

        sem_wait(&runC);
        fprintf(stdout, "Task C, round %ld of %ld\n", i, count); fflush(stdout);
        sem_post(&runA);

    }

    return NULL;
}

void *worker2(void *payload)
{
    const long count = (long)payload;
    long i;

    for (i = 1L; i <= count; i++) {

        sem_wait(&runB);
        fprintf(stdout, "Task B, round %ld of %ld\n", i, count); fflush(stdout);
        sem_post(&runC);

    }

    return NULL;
}


int main(void)
{
    pthread_t   thread1, thread2;
    int         result;
    long        repeat = 20L;

    if (sem_init(&runA, 0, 0) == -1 ||
        sem_init(&runB, 0, 0) == -1 ||
        sem_init(&runC, 0, 0) == -1) {
        fprintf(stderr, "Cannot initialize semaphores.\n");
        return 1;
    }

    result = pthread_create(&thread1, NULL, &worker1, (void *)repeat);
    if (result) {
        fprintf(stderr, "Failed to create first worker thread: %s.\n", strerror(result));
        return 1;
    }

    result = pthread_create(&thread2, NULL, &worker2, (void *)repeat);
    if (result) {
        fprintf(stderr, "Failed to create second worker thread: %s.\n", strerror(result));
        return 1;
    }

    fprintf(stdout, "Starting worker thread work.\n"); fflush(stdout);

    sem_post(&runA);

    result = pthread_join(thread1, NULL);
    if (result) {
        fprintf(stderr, "Failed to join first worker thread: %s.\n", strerror(result));
        return 1;
    }
        
    result = pthread_join(thread2, NULL);
    if (result) {
        fprintf(stderr, "Failed to join second worker thread: %s.\n", strerror(result));
        return 1;
    }

    fprintf(stdout, "All done.\n"); fflush(stdout);

    return 0;
}
If you save it as e.g. sem-test.c, you can compile and run it via e.g.
Code:
gcc sem-test.c -lpthread -Wall -O3 -o sem-test
./sem-test
Using a state variable (and mutex and condition variable to protect and wait upon it), the code would be
Code:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>

pthread_mutex_t run_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  run_wait = PTHREAD_COND_INITIALIZER;
enum {
    STATE_INITIAL = 0,
    STATE_A,
    STATE_B,
    STATE_C,
}               run_state = STATE_INITIAL;

void *worker1(void *payload)
{
    const long count = (long)payload;
    long       i = 1L;

    pthread_mutex_lock(&run_lock);

    while (i <= count) {

        if (run_state == STATE_A) {

            fprintf(stdout, "Task A, round %ld of %ld\n", i, count); fflush(stdout);
            run_state = STATE_B;
            /* No i++ here, since this thread does further tasks this round. */
            pthread_cond_signal(&run_wait);
            pthread_mutex_unlock(&run_lock);

        } else
        if (run_state == STATE_C) {

            fprintf(stdout, "Task C, round %ld of %ld\n", i, count); fflush(stdout);
            run_state = STATE_A;
            i++; /* Only on the last phase handled by this thread! */
            pthread_cond_signal(&run_wait);
            pthread_mutex_unlock(&run_lock);

        } else {

            pthread_cond_signal(&run_wait);
            pthread_cond_wait(&run_wait, &run_lock);
        }
    }

    return NULL;
}

void *worker2(void *payload)
{
    const long count = (long)payload;
    long       i = 1L;

    pthread_mutex_lock(&run_lock);

    while (i <= count) {

        if (run_state == STATE_B) {

            fprintf(stdout, "Task B, round %ld of %ld\n", i, count); fflush(stdout);
            run_state = STATE_C;
            i++; /* Only on the last phase handled by this thread! */
            pthread_cond_signal(&run_wait);
            pthread_mutex_unlock(&run_lock);

        } else {

            pthread_cond_signal(&run_wait);
            pthread_cond_wait(&run_wait, &run_lock);
        }
    }

    return NULL;
}

int main(void)
{
    pthread_t   thread1, thread2;
    int         result;
    long        repeat = 20L;

    result = pthread_create(&thread1, NULL, &worker1, (void *)repeat);
    if (result) {
        fprintf(stderr, "Failed to create first worker thread: %s.\n", strerror(result));
        return 1;
    }

    result = pthread_create(&thread2, NULL, &worker2, (void *)repeat);
    if (result) {
        fprintf(stderr, "Failed to create second worker thread: %s.\n", strerror(result));
        return 1;
    }

    fprintf(stdout, "Starting worker thread work.\n"); fflush(stdout);

    pthread_mutex_lock(&run_lock);
    run_state = STATE_A;
    pthread_cond_signal(&run_wait);
    pthread_mutex_unlock(&run_lock);

    result = pthread_join(thread1, NULL);
    if (result) {
        fprintf(stderr, "Failed to join first worker thread: %s.\n", strerror(result));
        return 1;
    }
        
    result = pthread_join(thread2, NULL);
    if (result) {
        fprintf(stderr, "Failed to join second worker thread: %s.\n", strerror(result));
        return 1;
    }

    fprintf(stdout, "All done.\n"); fflush(stdout);

    return 0;
}
Compile and run the same way as the other program.

The main difference between the approaches is that semaphores are more efficient for this, but impose a strict run order within each worker thread. This would not matter if each thread had only one task, but in your case, one thread has two tasks, A and C. (Also, pthread_ functions are not async-signal-safe, meaning they cannot be used reliably in signal handlers, but sem_post() is and can.)

Using the state variable, the task order within each thread is flexible; the tasks are done inside an if clause. This means that if you have a complex task "tree", which can take several different paths, you would have to be very careful with semaphores not to deadlock (because of the order they're handled in a single thread is broken), but state variable method handles that gracefully.

However, the state variable approach has an extra cost. When signaled, a random thread waiting on the condition variable is woken. You see in the example that if the state does not match any of the tasks this thread handles, it must signal the condition variable anyway, in the hope that it will wake up the correct thread. You can add fprintf(stdout,...);fflush(stdout) to the else cases in the state if clauses to see how often that happens.

The library/kernel implementations try very hard to avoid starvation -- meaning that eventually the correct thread should be woken, because even though the thread that gets woken up is undefined, each thread should be woken up in turn, eventually.

If you have just two or three threads, you can avoid that by using pthread_cond_broadcast() instead, which wakes up all threads. If you have many threads, that will cause "thundering herd" problems, because all threads will be woken in turn for each phase change. (It is not a problem if you have just two or three threads, but state changes will get slower the larger number of threads you have.)

You cannot use pthread mutexes like semaphores portably, because a mutex can only be unlocked by the same thread that locked it, while any thread can post or wait on any semaphore. (LinuxThreads "fast" mutexes (the default type) skip the owner check, so in practice in Linux you can unlock a mutex owned by another thread, but it is quite nonportable and nonstandard and might stop working in the future.)

Hope you find this informative,
 
Old 03-05-2012, 02:15 AM   #4
bigearsbilly
Senior Member
 
Registered: Mar 2004
Location: england
Distribution: Mint, Armbian, NetBSD, Puppy, Raspbian
Posts: 3,515

Rep: Reputation: 239Reputation: 239Reputation: 239
Quote:
Originally Posted by badmofo666 View Post
I'm trying to use the pthread library to accomplish something simple,
I think what we have here is a contradiction in terms ;-)
 
Old 03-05-2012, 02:01 PM   #5
badmofo666
Member
 
Registered: Mar 2004
Location: Texas, USA
Distribution: Ubuntu 12 (notebook), Debian Squeeze (home server), OpenSuse 12 (desktop)
Posts: 96

Original Poster
Rep: Reputation: 15
Quote:
Originally Posted by bigearsbilly View Post
I think what we have here is a contradiction in terms ;-)
As I have found out

Thanks nominal. I was unaware of semaphore.h. It was what I needed, and easier to use.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
CentOS 5.3 VM, trouble with ntpd, no server suitable for synchronization found lazybee26 Linux - Software 1 01-08-2010 08:37 PM
thread synchronization jkeertir Linux - Newbie 2 09-18-2009 02:17 AM
Thread Synchronization in Linux deepthisundareshan Linux - General 2 04-08-2009 11:08 PM
thread synchronization assignment help!! rossi143 Programming 2 03-25-2007 11:38 AM
thread Synchronization ej25 Programming 12 12-24-2004 01:11 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 03:14 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration