简体   繁体   English

为什么它不起作用? 简单的多线程示例

[英]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 .即使boo先开始运行,它也可能永远不会看到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 .如果您只有一个 CPU,那么当i==100时,CPU 不太可能从foo切换到boo

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.如果你有多个 CPU,那么i==100可能永远不会进入foo's缓存,因为i不是易失性的,并且互斥锁在读取之间没有锁定。

Really the compiler doesn't even have to read i after the first time, because there are no memory barriers.实际上,编译器在第一次之后甚至不必读取i ,因为没有 memory 障碍。 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.即使你要解决这个问题,在boo注意到之前i可能会增加超过 100 的明显可能性仍然存在。 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.所有i的访问都必须受到互斥量的保护,因此在if (i == 100) {行也是如此。 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.在没有同步的情况下,编译器可以自由地优化线程,就好像它在孤立地运行一样,并假设i永远不会改变。

  2. There is no guarantee that boo will start before foo .不能保证boo会在foo之前开始。 If it starts after, i will already be incremented well above 100 .如果它在之后开始, i将已经增加到远高于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 .这意味着foo可能会在boo有机会运行之前多次增加i ,因此boo看到的i的值可能很容易从0跳到1000 ,跳过所需的100

  4. In isolation, foo will "run away", incrementing i well beyond 5000 .孤立地, foo将“逃跑”,将i增加到远远超过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:例如,使用condition_variable s 在线程之间发出信号:

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 .问题是boo读取i的值, foo都读取和写入i的值,但是booif (i == 100)i的读取相对于发生在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> .这意味着使用某种形式的同步:要么将boo中的锁移到if之前,要么摆脱互斥并将i的类型更改为std::atomic<int>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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