简体   繁体   中英

Why it doesn't work? Simple multithreading example

can you help me with understanding why does this code freeze the program?

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

int i = 0;

mutex mx;

void foo() {
    while(1) {
        lock_guard<mutex> locker(mx);
        i++;
        if(i == 5000) {
            break;
        }
    }
}

void boo() {
    while(1) {
        if(i == 100) {
            lock_guard<mutex> locker(mx);
            i = 5000;
            break;
         }
    }
}

int main(int argc, char *argv[])
{
    thread th1(foo);
    thread th2(boo);

    th1.join();
    th2.join();

    return 0;
}

Why do I have such a result? How to change the code to make it right? Could you give me your thoughts. Thanks.

Even if boo starts running first, it will probably never see i==100 .

If you only have one CPU, then it's very unlikely that the CPU would be switched from foo to boo while i==100 .

If you have multiple CPUs, then i==100 will probably never even make it into foo's cache, because i is not volatile, and the mutex is not locked between reads.

Really the compiler doesn't even have to read i after the first time, because there are no memory barriers. It can assume that the value hasn't changed.

Even if you were to fix this, the distinct possibility would remain that i could be incremented past 100 before boo would notice. It looks like you expect the two threads to "take turns", but that's just not how it works.

There are a few concurrency issues with your solution:

  1. You have to lock the mutex consistently. All access to i must be protected by the mutex, so also at the if (i == 100) { line. In the absence of synchronization, the compiler is free to optimize the thread as-if it was running in isolation, and assume i to never change.

  2. There is no guarantee that boo will start before foo . If it starts after, i will already be incremented well above 100 .

  3. Mutex locking is not guaranteed to be fair. Two threads competing for the same mutex will not run in an interleaved manner. Which means foo might increment i many times before boo gets a chance to run, so the value of i as seen by boo might easily jump from 0 to 1000 , skipping the desired 100 .

  4. In isolation, foo will "run away", incrementing i well beyond 5000 . There should be some exit or a restart condition.

How to change the code to make it right?

Add some synchronization in order to enforce interleaved processing. For example, using condition_variable s to signal between threads:

int i = 0;

mutex mx;
condition_variable updated_cond;
bool updated = false;
condition_variable consumed_cond;
bool consumed = true;

void foo() {
    while (1) {
        unique_lock<mutex> locker(mx);
        consumed_cond.wait(locker, [] { return consumed; });
        consumed = false;
        if (i == 5000) {
            break;
        }
        std::cout << "foo: i = " << i << "+1\n";
        i++;
        updated = true;
        updated_cond.notify_one();
    }
    std::cout << "foo exiting\n";
}

void boo() {
    for (bool exit = false; !exit; ) {
        unique_lock<mutex> locker(mx);
        updated_cond.wait(locker, [] { return updated; });
        updated = false;
        std::cout << "boo: i = " << i << "\n";
        if (i == 100) {
            i = 5000;
            exit = true;
        }
        consumed = true;
        consumed_cond.notify_one();
    }
    std::cout << "boo exiting\n";
}

The behavior of the program is undefined, so reasoning about what it does is futile. The problem is that boo reads the value of i and foo both reads and writes the value of i , but the read of i in if (i == 100) in boo is unsequenced with respect to the writes occurring in foo . That's a data race, and the behavior of the program is undefined. Sure, you can guess at what might happen, but if you want your code to run correctly, you have to ensure that there are no data races. That means using some form of synchronization: either move the lock in boo before the if , or get rid of the mutex and change the type of i to std::atomic<int> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM