首页 > 解决方案 > 竞争条件 2 个线程交替

问题描述

所以我希望程序输出 1\n2\n1\n2\n1\n2\n 但它似乎卡在某个地方。但是当我调试它并在声明 t2 后立即在 cv1.notify_one() 处设置一个断点时,它会执行吗?

#include <iostream> #include <mutex> #include <thread> #include <condition_variable> using namespace std; mutex cout_lock; condition_variable cv1, cv2; mutex mtx1; unique_lock<std::mutex> lck1(mtx1); mutex mtx2; unique_lock<std::mutex> lck2(mtx2); const int COUNT = 3; int main(int argc, char** argv) { thread t1([&](){ for(int i = 0; i < COUNT; ++i) { cv1.wait(lck1); cout << "1" << endl; cv2.notify_one(); } }); thread t2([&](){ for(int i = 0; i < COUNT; ++i) { cv2.wait(lck2); cout << "2" << endl; cv1.notify_one(); } }); cv1.notify_one(); t1.join(); t2.join(); return 0; }

标签: c++

解决方案


我认为您的线程开始和调用 cv1.notify_one();in之间存在数据竞争main()

考虑cv1.notify_one()在线程 1 开始并调用之前调用发生的情况cv1.wait()。之后没有人再打电话cv1.notify了,你的简历就在等着。这称为丢失的唤醒

您需要一种机制在 main 中等待,直到两个线程都启动,然后执行cv1.notify()

下面是使用 int 和互斥锁的示例。

#include "pch.h"

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

using namespace std;

condition_variable cv1, cv2;
mutex m;

const int COUNT = 3;

enum Turn
{
    T1,
    T2
};

int main(int argc, char** argv)
{
    mutex thread_start_mutex;
    int num_started_threads = 0;
    Turn turn = T1;

    thread t1([&]() {
        {
            // increase the number of started threads
            unique_lock<std::mutex> lck(thread_start_mutex);
            ++num_started_threads;
        }

        for (int i = 0; i < COUNT; ++i)
        {
            // locked cout, unlock before calling notify
            {
                unique_lock<std::mutex> lck1(m);
                // wait till main thread calls notify
                cv1.wait(lck1, [&] { return turn == T1;});
                cout << "1 a really long string" << endl;
                turn = T2; // next it's T2's turn
            }
            cv2.notify_one();
        }
    });

    thread t2([&]() {
        {
            // increase the number of started threads
            unique_lock<std::mutex> lck(thread_start_mutex);
            ++num_started_threads;
        }

        for (int i = 0; i < COUNT; ++i)
        {
            // locked cout, unlock before calling notify
            {
                unique_lock<std::mutex> lck2(m);
                cv2.wait(lck2, [&] {return turn == T2;});
                cout << "2 some other stuff to test" << endl;
                turn = T1;
            }
            cv1.notify_one();
        }
    });

    unique_lock<std::mutex> lck(thread_start_mutex);
    // wait until both threads have started
    cv1.wait(lck, [&] { return num_started_threads == 2; });
    lck.unlock();
    cv1.notify_one();

    t1.join();
    t2.join();

    return 0;
}

此外,还不清楚为什么您有两个互斥锁被锁定在 main 之外。我通常认为互斥锁是一种受保护的资源,不应同时访问。似乎这个想法是为了保护 cout 调用,您应该为此使用一个互斥锁,每个线程将锁定、执行 cout、解锁并通知另一个。

编辑

我的原始答案在调用 t1.notify() 和 t2.wait() 之间存在完全相同的问题。如果 t1.notify() 在线程 2 等待之前被调用,线程 2 永远不会被唤醒。

为了解决这个问题,我添加了一个枚举“Turn”,它指示轮到谁了,每个等待条件现在检查是否轮到他们。如果是,他们不会等待而只是打印出来,所以即使错过了通知,他们仍然会完成他们的任务。如果不是轮到他们,他们将阻塞,直到其他线程设置轮到变量并调用通知。

注意:这展示了一个很好的示例/实践,即在使用 cv.wait() 时有条件通常要好得多。这既使意图清晰,又避免了丢失的唤醒和虚假的唤醒。

注意 2这个解决方案可能过于复杂,一般来说条件变量和互斥锁不太可能是解决这个问题的最佳方法。


推荐阅读