比赛条件2线程交替

问题描述 投票:3回答:3

所以我希望程序输出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++
3个回答
2
投票

有几个缺点:

  1. 你想保护你的输出。因此,您只需要一个互斥锁,因此一次只能有一个线程可以完成工作。
  2. 您可能错过了对条件变量的通知。
  3. 你的全球unique_locks在他们的构造函数中获取互斥锁的锁。所以你一直持有锁,没有线程可以取得进展。 你的全球unique_locks在他们的构造函数中获取互斥锁的锁。这是在主线程中完成的。 T1和T2正在通过condition_variable解锁它们。这是未定义的行为(拥有互斥锁的线程必须将其解锁)。

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

  1. 有一个你感兴趣的条件。在这种情况下,某种变量可以记住是谁。
  2. 通过(ONE!)mutex保护这个变量
  3. 使用(ONE!)condition_variable结合第2点的互斥锁和第1点的条件。

这可以确保:

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

这导致以下代码(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
投票

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

考虑cv1.notify_one()调用在线程1启动之前发生并调用cv1.wait()的情况。之后没有人再打电话给cv1.notify而且你的cv-s只是在等待。这称为失落唤醒。

你需要一个机制在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:这个解决方案可能过于复杂,一般来说条件变量和互斥量不太可能是解决这个问题的最佳方法。


-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;
}
© www.soinside.com 2019 - 2024. All rights reserved.