在单独的线程上解锁时,互斥锁未解锁。C++

3

我试图理解为什么我的互斥锁的行为不符合我的预期。

我正在调试另一个问题,并决定创建一个非常简单的可执行文件来直接测试互斥锁的行为。以下是我想到的:

#include <mutex>
#include <thread>
#include <iostream>
#include <chrono>

int main(int argc, char** argv)
{
        std::mutex myMutex;

        auto threadGenerator = [&] (std::string printout)
        {
                auto threadFunctor = [&, printout] {

                        int count = 0;
                        while (count < 300)
                        {
                                std::lock_guard<std::mutex> lock(myMutex);


                                std::cout << printout << std::endl;
                                count++;

                                // Sleep ensures that the other thread will be waiting on mutex
                                // when I release lock
                                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                        }
                };

                return threadFunctor;
        };

        auto thread1Functor = threadGenerator("Thread 1 got lock");
        auto thread2Functor = threadGenerator("Thread 2 got lock");

        std::thread thread1(thread1Functor);
        std::thread thread2(thread2Functor);

        thread1.join();
        thread2.join();

        return 0;
}

这只是产生了两个线程,它们重复地锁定和解锁互斥锁,并打印一些输出。我添加了睡眠以强制lock_guard阻塞并使线程等待彼此。

这将产生以下输出:

Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock
Thread 1 got lock

最终,一旦线程1完成,线程2将再次开始获取锁。

它们不应该在线程1和线程2之间交替吗?锁应该在每个循环迭代结束时释放,这应该允许另一个线程控制互斥量。为什么没有发生?有没有办法让它发生?


是的,那样会解决问题,但是有必要这样做吗?每当使用互斥锁时,有必要为它们添加睡眠周期才能使它们正常工作吗? - user2445507
@user2445507 不,这不是必要的。 - Slava
2
std::mutex 不保证公平性。 - Pete Becker
显然,这是玩具代码,用于测试一个概念,但如果你想要线程A,然后B,再回到A等等...并且它们从未同时运行,那么你可能根本不需要线程。你需要一个状态机。 - user4581301
1
我觉得我需要一个条件变量才能真正让这个工作按预期进行。但无论如何,这只是一个人为的例子。现在我知道互斥锁并不能保证公平性,所以我会为此做出计划。 - user2445507
3个回答

3

它们难道不应该在线程1和线程2之间交替执行吗?

不,没有这样的保证。

每次循环迭代结束时都应该释放锁,这样应该允许另一个线程控制互斥量。为什么不会发生这种情况?

因为您的第一个线程锁定了互斥量,睡眠,解锁了互斥量,然后尝试再次锁定互斥量。现在线程1和线程2都在尝试获取互斥量,但线程1处于运行状态,而线程2正在睡眠,因此线程1更有可能先获取互斥量。

有没有办法使它发生?

您的程序不应区分线程,也不应依赖顺序。在真实情况下,多个线程等待互斥量以获取数据,并且一个线程将其放置在那里,因此所有等待线程都处于等待状态,因此它们获得互斥量的概率相似。但是这可能是特定硬件、操作系统和版本的。您的程序不应依赖于哪个特定线程获得了互斥量。


2

在锁定互斥锁时,将代码放在单独的范围内:

while (count < 300) {
    {
        std::lock_guard<std::mutex> lock(myMutex);

        std::cout << printout << std::endl;
        count++;
    } // lock is released here
    // ...
}

1
只有在我保留“//…”中的睡眠时,才能解决这个问题,但我不想这样做,因为代码中会有很多睡眠。 - user2445507
我不想使用sleep,因为它会减慢我的程序。至少,我想知道我可以添加的最小sleep时间以确保程序正常运行。 - user2445507
@user 检查在释放锁之后使用std::this_thread::yield()是否适用于您的情况。 - πάντα ῥεῖ
那似乎确实有所改善。我仍然从同一线程获得长串的打印输出。不过,现在每个串只有30-40条,而不是全部300条都在一起了。虽然如此,我仍然希望完全没有这样的链。 - user2445507
@user2445507 你正在错误的方向上努力。你的模拟有误,所以你不需要让它工作,你需要创建正确的模拟。 - Slava

0
正如@πάνταῥεῖ所说,每次线程进入While循环时,作用域都由While的部分限定。因此,std::lock_guard<std::mutex>在while中的代码完成后被销毁,每次线程再次执行while部分时,都会创建一个新的作用域。
根据CppReference http://en.cppreference.com/w/cpp/thread/lock_guard

当控制离开创建lock_guard对象的作用域时,lock_guard将被销毁并释放互斥锁。 lock_guard类是不可复制的。

这就是你出现这种行为的原因。

enter image description here

如果你想锁定所有的threadFunctor,你应该将lock_guard从while循环中取出,像这样:
std::mutex myMutex;

auto threadGenerator = [&](std::string printout)
{
    auto threadFunctor = [&, printout] {
        //Scope of threadFunctor
        int count = 0;
        std::lock_guard<std::mutex> lock(myMutex); //The Lock is working into the Scope of threadFunctor

        while (count < 100)
        {
            //New Scope is create (While's Scope)
            std::cout << printout << std::endl;
            count++;
            // Sleep ensures that the other thread will be waiting on mutex
            // when I release lock
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        //At this point the Lock will be released.
    };

    return threadFunctor;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接