1

I have two threads and one CPU.

I want each of the two threads that reached line A earlier in their program to wait for the other thread to reach line A, after which both threads continue to run their program. I have done this as follows, But I want both threads of line A to run their program exactly at the same time.

How can I accomplish this?

My code:

//headers
static volatile bool waitFlag[2];

void *threadZero(void*){
    //some codes

    waitFlag[1] = true;
    while(!waitFlag[0]);
    //line A of thread zero

    //some codes  
}


void *threadOne(void*){
    // some codes

    waitFlag[0] = true;
    while(!waitFlag[1]);
    //line A of thread one

    //some codes
}


int main(){
    waitFlag[0] = waitFlag[1] = false;
    //Creates two threads and waits for them to finish.
}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
mehran
  • 191
  • 10
  • 1
    so note that just using plain `bool` is not thread safe and will cause a data race and thus undefined behavior. In C you should use either OS primitives or [C11 atomics](https://en.cppreference.com/w/c/atomic) (which are generally supported) – Mgetz Nov 30 '21 at 14:02
  • 1
    Re "*OS primitives*", This is referring to mutex, semaphores, etc. If non-trivial waiting is involved, these are more appropriate than a busy-wait loop using atomics. – ikegami Nov 30 '21 at 14:13
  • Mgetz , why cause a data race? I used two bool variables (wait Flag[0] and white Flag[1]) to race condition not happen. – mehran Nov 30 '21 at 14:19
  • 2
    @mehran because `bool` is not thread safe, the CPU may not see the update across threads. The C standard explicitly says that for something to not cause a data race it either needs to be behind a barrier (OS Primitive) or use atomics if it's accessed from multiple threads. – Mgetz Nov 30 '21 at 14:20
  • 1
    https://stackoverflow.com/questions/26231727/what-is-the-correct-way-to-implement-thread-barrier-and-barrier-resetting-in-c – Hans Passant Nov 30 '21 at 14:23
  • 1
    If you're willing to switch to C++20, [`std::latch`](https://en.cppreference.com/w/cpp/thread/latch) has this all wrapped up in a nice package. Note that it still will not ensure that the two threads literally run simultaneously; that is always at the mercy of the OS scheduler. – Nate Eldredge Nov 30 '21 at 14:40
  • 1
    @mehran To clarify a bit why the bool variables alone are not atomic, it is because the caching system within the CPU packages can copy this item, creating different values of the same item for the different cores. The writes will eventually update the shared system RAM, but the writes will occur at a time that cannot be placed before, after, or during the the read (which is subject to caching). Non-cached memory access is required for this to work, which has special semantics, and can be achieved with a mutex or similarly designed special data type. – Edwin Buck Nov 30 '21 at 17:29

1 Answers1

3

A busy loop is inefficient for this purpose. What you want is a condition, a mutex and a simple counter:

  1. Lock the mutex.
  2. Increase the counter.
    • If the counter is 2, broadcast on the condition.
    • Otherwise wait on the condition.
  3. Unlock the mutex.

This logic can be easily adapted to any number of threads by changing the threshold for the counter. The last thread to increment the counter (protected by the mutex) will broadcast on the condition and will unlock itself and all the other threads simultaneously. If you want to synchronize multiple times you can also reset the counter.

Here's an example:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/random.h>

pthread_cond_t cond;
pthread_mutex_t cond_mutex;
unsigned int waiting;

// Sleep a random amount between 0 and 3s.
// This is just for testing, you don't actually neeed it.
void waste_time(void) {
    unsigned us;
    getrandom(&us, sizeof(us), 0);
    us %= 3000000;
    fprintf(stderr, "[%lx] Sleeping %u us...\n", pthread_self(), us);
    usleep(us);
}

void synchronize(void) {
    pthread_mutex_lock(&cond_mutex);

    if (++waiting == 2) {
        pthread_cond_broadcast(&cond);
    } else {
        while (waiting != 2)
            pthread_cond_wait(&cond, &cond_mutex);
    }

    pthread_mutex_unlock(&cond_mutex);
}

void *threadZero(void *_) {
    waste_time();
    // ...
    synchronize();
    fprintf(stderr, "[%lx] Resuming.\n", pthread_self());
    // ...
    return NULL;
}


void *threadOne(void *_) {
    waste_time();
    // ...
    synchronize();
    fprintf(stderr, "[%lx] Resuming.\n", pthread_self());
    // ...
    return NULL;
}


int main(void) {
    pthread_t zero, one;

    pthread_create(&zero, NULL, threadZero, NULL);
    pthread_create(&one, NULL, threadOne, NULL);
    // ...
    pthread_join(zero, NULL);
    pthread_join(one, NULL);

    return 0;
}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • @ikegami ah, right! Thank you for the edit. – Marco Bonelli Nov 30 '21 at 14:21
  • Note: While [C11 did introduce primitives for this](https://en.cppreference.com/w/c/thread) they are not yet cross platform and have limited support currently as of 2021. – Mgetz Nov 30 '21 at 14:22