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,