竞态条件2线程交替

时间:2019-04-09 22:34:02

标签: c++

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

#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; }

3 个答案:

答案 0 :(得分:2)

有几个缺陷:

  1. 您想保护自己的输出。因此,您只需要一个互斥体,因此一次只有一个线程可以完成其工作。
  2. 您可能会丢失有关条件变量的通知。
  3. 您的全局unique_lock会获取互斥锁的构造函数中的锁。因此,您一直都在持有锁,并且没有线程可以取得进展。您的全局unique_lock获取了互斥锁在其构造函数中的锁。这是在主线程中完成的。 T1和T2正在通过condition_variable对其进行解锁。这是未定义的行为(拥有互斥锁的线程必须将其解锁)。

这是正确使用条件变量方法的诀窍:

  1. 有一个您感兴趣的条件。在这种情况下,请记住某种变量,以记住是谁。
  2. 用(ONE!)mutex
  3. 来保护此变量。
  4. 与点2的互斥量和点1的条件结合使用(ONE!)condition_variable

这可以确保:

  • 在任何时候,只有一个线程可以查找和/或更改您的状况。
  • 如果线程到达代码中可能等待条件变量的位置,则它将首先检查条件。也许线程甚至不需要睡觉,因为他想要等待的条件已经成立。为此,线程必须获取互斥量,检查条件并决定要执行的操作。这样做时,他拥有锁。条件无法更改,因为线程本身具有锁。因此,您不能错过通知。

这将导致以下代码(see live here):

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

using namespace std;

int main(int argc, char** argv)
{
    condition_variable cv;
    mutex mtx;
    bool runt1 = true;
    bool runt2 = false;
    constexpr int COUNT = 3;

    thread t1([&]()
    {
        for(int i = 0; i < COUNT; ++i)
        {
            unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&](){ return runt1; });
            cout << "1" << endl;
            runt1 = false;
            runt2 = true;
            lck.unlock();
            cv.notify_one();
        }
    });

    thread t2([&]()
    {
        for(int i = 0; i < COUNT; ++i)
        {
            unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&](){ return runt2; });
            cout << "2" << endl;
            runt1 = true;
            runt2 = false;   
            lck.unlock();
            cv.notify_one();
        }
    });

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

    return 0;
}

答案 1 :(得分:1)

我认为您在线程开始与cv1.notify_one();中对main()的调用之间存在数据争夺。

考虑在线程1启动并调用cv1.notify_one()之前发生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()之间存在完全相同的问题。 如果在线程2等待之前调用了t1.notify(),则线程2永远不会被唤醒。

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

注意:这证​​明了一个很好的例子/做法,通常在使用cv.wait()时有条件会更好。这既使意图清晰,又避免了“丢失的唤醒”和“虚假的唤醒”。

注2 ,该解决方案可能过于复杂,一般情况下,变量和互斥体不太可能是解决此问题的最佳方法。

答案 2 :(得分:-1)

另一个答案在概念上是正确的,但仍然存在另一个种族条件。我运行了代码,它仍然会死锁。

问题是创建了t1,但是直到执行cv1.wait(lck1)之后,它才进入cv1.notify_one()。因此,您的两个线程永远坐在一起等待。当您将断点放在该行上时,您可以演示这一点,以使线程赶上。同样,当一个线程完成但没有给另一时间调用wait()时,此问题仍然存在,因此仅调用notify_one。通过添加来自usleep(100)的一些unistd.h调用,可以看出这也是固定的*(松散使用)。

参见下文:

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>

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\n";
            usleep(100);
            cv2.notify_one();
        }
    });

    thread t2([&](){
        for(int i = 0; i < COUNT; ++i)
        {

            cv2.wait(lck2);
            cout << "2\n";
            usleep(100);
            cv1.notify_one();
        }
    });

    usleep(1000);
    cv1.notify_one();

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

    return 0;
}

编辑:要做得更好将是检查等待的线程,该线程未内置在您使用的互斥锁中。正确的方法可能是创建自己的互斥包装器类,并将该功能包括在类中,但是为了简单起见,我只做了一个waiting变量。

参见下文:

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>

using namespace std;

mutex cout_lock;

condition_variable cv1, cv2, cv3;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
int waiting = 0;
const int COUNT = 3;

int main(int argc, char** argv)
{

    thread t1([&](){
        for(int i = 0; i < COUNT; ++i)
        {
            waiting++;
            cv1.wait(lck1);
            cout << "1\n";
            waiting--;
            if(!waiting)
                usleep(100);
            cv2.notify_one();
        }
    });

    thread t2([&](){
        for(int i = 0; i < COUNT; ++i)
        {
            waiting++;
            cv2.wait(lck2);
            cout << "2\n";
            waiting--;
            if(!waiting)
                usleep(100);
            cv1.notify_one();
        }
    });

    if(!waiting)
        usleep(100);
    cv1.notify_one();

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

    return 0;
}