【问题标题】:Multi-thread. Undefined behaviour with two while loops in two different thread多线程。两个不同线程中的两个 while 循环的未定义行为
【发布时间】:2021-10-25 02:49:49
【问题描述】:
#include <mutex>

using namespace std;

int counter = 0;
mutex m;

void thread1() {
    while(1) {
        m.lock();
        cout << "th1 " << counter << endl;
        ++counter;
        if (counter == 300) {
            m.unlock();
            break;
        }
        m.unlock();
    }
}

void thread2() {
    while(1) {
        m.lock();
        cout << "th2 " << counter << endl;
        if (counter == 10) {
            counter = 300;
            m.unlock();
            break;
        }
        m.unlock();
    }
}

int main(int argc, const char * argv[]) {

    thread th1(thread1);
    thread th2(thread2);
    th1.join();
    th2.join();
    return 0;
}

我正在使用 while 循环运行两个线程。为什么尽管我使用 mutex lock 和 unlock 这段代码的结果每次都不同。 来自函数 thread1 的第一个代码被执行 X 次,然后来自函数 thread2 的代码被执行 y 次。 他们不应该一个接一个地执行吗.. thread1 -> thread2 -> thread1 -> 等等?

【问题讨论】:

  • 我了解到您没有未定义的行为,而是已定义的行为:调度不确定。
  • 测试“if (counter == 10)”可能永远不会变为真,因为 thread1 在重新调度 thread2 之前愉快地将 counter 增加到 10 以上。
  • 我认为你对线程如何工作的想法是错误的。没有什么可以说线程 1 的循环会发生,然后线程 2 的循环会立即发生,它们会继续像那样交换。
  • 操作系统调度线程的时序。 thread1 可以在 thread2 计划运行之前轻松运行其循环的多次迭代,反之亦然。一旦它们都运行,操作系统将在自己选择的时间在线程之间交换。 mutex 只保证两个线程不能同时访问counter(或cout)。它对控制循环迭代的时间没有任何作用。
  • 显然,如果您想在两个线程之间严格交替,编写单个循环(在单个线程中)会更简单高效。

标签: c++ multithreading while-loop mutex


【解决方案1】:

这是非确定性的,因为您有竞争条件。

每次您解锁互斥锁时,两个线程都会竞相再次锁定它。

此外,在您的代码中,您可以增加超过 10 并错过 thread2 中的退出条件,这不是我认为您想要的。

使用条件变量 (std::condition_variable) 可以将执行同步为您所描述的内容(线程 1、线程 2、线程 1 等)的一种方法。

这是您的代码,稍作修改即可获得同步行为:

#include <mutex>
#include <iostream>
using namespace std;

int counter = 0;
bool flag = false;
mutex m;
condition_variable c;

void thread1() {
    while(1) {
        unique_lock<mutex> lk(m);
        c.wait(lk, []{ return flag; });
        cout << "th1 " << counter << endl;
        if (counter == 300) {
            flag=!flag;
            lk.unlock();
            c.notify_one();
            break;
        }
        counter++;
        flag=!flag;
        lk.unlock();
        c.notify_one();
    }
}

void thread2() {
    while(1) {
        unique_lock<mutex> lk(m);
        c.wait(lk, []{ return !flag; });
        cout << "th2 " << counter << endl;
        if (counter == 10) {
            counter = 300;
            flag=!flag;
            lk.unlock();
            c.notify_one();   
            break;
        }
        flag=!flag;
        lk.unlock();
        c.notify_one();
    }
}

int main(int argc, const char * argv[]) {

    thread th1(thread1);
    thread th2(thread2);
    th1.join();
    th2.join();
    return 0;
}

还有输出:

th2 0
th1 0
th2 1
th1 1
th2 2
th1 2
th2 3
th1 3
th2 4
th1 4
th2 5
th1 5
th2 6
th1 6
th2 7
th1 7
th2 8
th1 8
th2 9
th1 9
th2 10
th1 300

然后程序退出...

几点说明:

线程在bool flag 变量上同步。

flag 用于创建在条件变量上调用的等待方法中使用的谓词

c.wait(lk, []{ return flag; });

这只是一个返回标志当前值的 lambda。

th1 在flag==true 上触发,th2 在flag==false 上触发。

notify_one 方法告诉等待线程检查它的条件。

我们使用unique_lock 实例,因为我们需要能够根据条件手动锁定/解锁。

【讨论】:

  • 我不认为它是 undefined 行为,而是 non-deterministic 行为,这在进行多线程编程时很常见并且通常是预期的。
  • 谢谢@JeremyFriesner。什么会使这个不确定?如果他有相同的代码但没有互斥锁阻止对counter 变量的访问?
  • @Fermi-r 是的,那会这样做......在 C++ 语言律师行话中,“未定义的行为”具有特定含义,类似于“语言规范说你不应该做那个,而且你还是做了,所以你的程序是错误的,因此无论它做什么或不做什么都是你的错,我们不会洗手”。有很多错误会引发未定义的行为,但只是让两个线程同时尝试获取相同的互斥锁并不是其中之一(因为虽然无法预测哪个会先获得锁,但它仍然是合法的/定义明确的 C++ 代码)
猜你喜欢
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 2017-11-15
  • 2012-10-28
  • 1970-01-01
  • 2021-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多