这段代码说明了互斥锁正在两个线程之间共享,但是thread_mutex周围的作用域块发生了一些奇怪的事情。

(我在another question中有此代码的变体,但这似乎是第二个谜。)

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

#include <unistd.h>

    int main ()
    {
        std::mutex m;

        std::thread t ([&] ()
        {
            while (true)
            {
                {
                    std::lock_guard <std::mutex> thread_lock (m);

                    usleep (10*1000); // or whatever
                }

                std::cerr << "#";
                std::cerr.flush ();
            }
        });

        while (true)
        {
            std::lock_guard <std::mutex> main_lock (m);
            std::cerr << ".";
            std::cerr.flush ();
        }
    }

实际上,这基本上是可行的,但是从理论上讲,不需要在thread_lock周围定义作用域。但是,如果您将其注释掉...
#include <thread>
#include <mutex>
#include <iostream>

#include <unistd.h>

int main ()
{
    std::mutex m;

    std::thread t ([&] ()
    {
        while (true)
        {
//          {
                std::lock_guard <std::mutex> thread_lock (m);

                usleep (10*1000); // or whatever
//          }

            std::cerr << "#";
            std::cerr.flush ();
        }
    });

    while (true)
    {
        std::lock_guard <std::mutex> main_lock (m);
        std::cerr << ".";
        std::cerr.flush ();
    }
}

输出是这样的:
........########################################################################################################################################################################################################################################################################################################################################################################################################################################################################################

即似乎thread_lock从不产生main_lock

如果除去了冗余作用域块,为什么thread_lock总是获得锁定而main_lock总是等待?

最佳答案

我在Linux上使用pthreads在带有GCC(7.3.0)的Linux上测试了您的代码(已删除块作用域),并得到了与您相似的结果。主线程很饿,尽管如果我等待足够长的时间,我偶尔会看到主线程做了一些工作。

但是,我在Windows上使用MSVC(19.15)运行了相同的代码,并且没有线程出现饥饿。

看来您使用的是posix,所以我猜您的标准库在后端使用了pthreads吗? (即使使用C++ 11,我也必须链接pthread。)Pthread互斥体不能保证公平。但这只是故事的一半。您的输出似乎与usleep调用有关。

如果我取出usleep,我会看到公平(Linux):

    // fair again
    while (true)
    {
        std::lock_guard <std::mutex> thread_lock (m);
        std::cerr << "#";
        std::cerr.flush ();
    }

我的猜测是,由于在保持互斥锁的状态下睡了很长时间,实际上可以保证主线程将是,因为blocked可以是。想象一下,起初主线程可能会尝试旋转,以期希望互斥锁将很快可用。一段时间后,它可能会被放入等待列表中。

在辅助线程中,lock_guard对象在循环结束时被破坏,因此互斥体被释放。它将唤醒主线程,但是它将立即构造一个新的lock_guard,再次锁定互斥锁。主线程不太可能捕获互斥锁,因为它是已调度的。因此,除非在此小窗口中发生上下文切换,否则辅助线程可能会再次获取互斥体。

在带有作用域块的代码中,辅助线程中的互斥锁在IO调用之前被释放。在屏幕上打印需要很长时间,因此主线程有很多时间来捕获互斥量。

正如@Ted Lyngmo在他的回答中所说,如果在lock_guard创建之前添加 sleep ,那么饥饿的可能性就会大大降低。
    while (true)
    {
        usleep (1);
        std::lock_guard <std::mutex> thread_lock (m);
        usleep (10*1000);
        std::cerr << "#";
        std::cerr.flush ();
    }

我也尝试使用yield,但是我需要5+来使它更公平,这使我相信实际的库实现细节,OS调度程序以及缓存和内存子系统效果还有其他细微差别。

顺便说一句,谢谢你的提问。测试和玩起来真的很容易。

关于c++ - 为什么多余的额外作用域块会影响std::lock_guard行为?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53068844/

10-11 23:10
查看更多