锁定可用时立即获取

Posted

技术标签:

【中文标题】锁定可用时立即获取【英文标题】:Acquire lock as soon as it's available 【发布时间】:2013-04-05 15:27:20 【问题描述】:

我有两个线程试图锁定同一个boost::mutex。其中一个线程持续处理一些数据,另一个线程定期显示当前状态。处理线程,按照我的意图,非常频繁地释放锁并重新获取它,以便显示线程可以在需要它的时候进入并获取它。所以,很明显,我希望显示线程在下一次被进程线程释放时获取锁。但是,它并没有这样做,而是等待锁,并且仅在进程线程的多个锁释放周期后才获取它。

请检查说明我的问题的最小示例:

#include <boost/thread.hpp>
#include <iostream>

using namespace std;
using namespace boost;

mutex mut;

void process() 
        double start = time(0);
        while(1) 
                unique_lock<mutex> lock(mut);
                this_thread::sleep(posix_time::milliseconds(10));
                std::cout<<".";
                if(time(0)>start+10) break;
        


int main() 

        thread t(process);

        while(!t.timed_join(posix_time::seconds(1))) 
                posix_time::ptime mst1 = posix_time::microsec_clock::local_time();
                cout<<endl<<"attempting to lock"<<endl;
                cout.flush();

                unique_lock<mutex> lock(mut);

                posix_time::ptime mst2 = posix_time::microsec_clock::local_time();
                posix_time::time_duration msdiff = mst2 - mst1;
                cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl;
                cout.flush();
        


编译:g++ mutextest.cpp -lboost_thread -pthread

当我运行可执行文件时,示例输出如下:

...................................................................................................
attempting to lock
....................................................................................................................................................................................................................................................................................................................................................................................................................................
acquired lock in: 4243
...................................................................................................
attempting to lock
........................................................................................................
acquired lock in: 1049
...................................................................................................
attempting to lock
........................................................................................................................
acquired lock in: 1211
....................................

如您所见,在最坏的情况下,显示线程会等待 424 个锁释放周期,然后才开始捕获锁。

我显然以错误的方式使用互斥锁,但解决此问题的常用方法是什么?

【问题讨论】:

除了通常的怀疑之外:(条件变量,yield,)尝试一些线程安全容器...... 【参考方案1】:

并不是你使用错了互斥锁,只是线程没有达到你期望的效果。操作系统决定何时运行哪个线程(这称为“调度”),并且代码中没有任何内容会在循环结束时强制线程切换;线程继续运行,并重新获得锁。要尝试的一件事是在释放锁之后(在这种情况下,在循环顶部,在重新锁定之前)添加对this_thread::yield() 的调用;这将向调度程序建议它适合另一个线程运行。如果你真的想要紧密同步的交错,线程不会这样做;相反,编写一个更高级别的函数,一个接一个地调用你的两个函数。

【讨论】:

yield() 似乎有所帮助,但仍然很糟糕。在处理线程中的unique_lock 构造之前使用yield(),在显示线程捕获锁之前,我仍然看到多达~300 次锁释放迭代。我真的不想要超紧密的交错,因为它只是一个显示,但如果它不等待 300 次该死的迭代,我会很感激 :) 哎呀。尝试拨打sleep;当一个线程休眠时,任何合理的操作系统都会运行其他线程。 我也试过了,如果你睡0毫秒,就好像睡眠不存在,否则你至少可以睡10毫秒,浪费太多了这样的麻烦:/我的意思是,我并不是在要求理论上的不可能,锁授予实现只需要将它交给最早的候选人。【参考方案2】:

如果更新线程无事可做,它可以等待互斥体可用。

查看 boost::condition_variable。你可以在这里读到它 http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html 和这里 Using boost condition variables

如果让更新线程进入休眠状态会是一个问题——它在许多 GUI 系统上并且你没有指定你正在使用哪个——考虑从处理线程发布一条消息到更新线程。 (同样,详细信息取决于您的平台。)该消息可能包含更新显示所需的信息,或者是“现在是查看的好时机”的通知。

如果您确实使用条件代码,则处理线程可能应该在发出条件信号之后并在重新获取锁以为更新线程打开一个大窗口之前让步。

【讨论】:

请注意,我的更新线程已经在等待互斥锁变得可用,但问题是即使在它可用之后,它也很长时间没有获取锁。如果 GUI 系统真的很重要,我的显示线程会使用 OpenGL 进行一些屏幕绘制。 处理线程将消息发布到更新线程将无济于事,真的。显示线程不能在不获取锁的情况下“查看”​​,这就是问题所在。【参考方案3】:

您可能想看看boost::condition_variable,特别是wait()notify_one()notify_all() 方法。 wait() 方法将阻塞当前线程,直到锁被释放。方法notify_one()notify_all() 通知一个或所有等待锁继续执行的线程。

【讨论】:

我已经尝试过condition_variable,但它不起作用。 notify_x 方法使其他线程进入“就绪”状态,但它们不会强制唤醒。无论哪种方式,在等待之前您首先需要锁,我的问题是无法获取锁。【参考方案4】:

在我看来,互斥锁的原则不是公平,而是正确。互斥锁本身无法控制调度程序。困扰我的一件事是您选择创建 2 个使用这种粗粒度锁定的线程的原因。从理论上讲,您是在并行运行两件事,但实际上您是在使它们相互依赖/串行。

Pete 的想法似乎更好(一个函数运行这些绘制和更新),您仍然可以在每个内部函数中使用线程,而不必担心争用和公平。

如果您真的希望让两个线程并行运行,那么我可以给您一些提示:

使用原子自旋锁,忘记互斥锁, 进入实现定义的区域并设置线程优先级,或者 使锁定更加细粒度。

不幸的是,这些都不是万无一失的。

【讨论】:

感谢您的建议,它们都有有用的案例,但不是我的:自旋锁太浪费了,我想保持便携性,并且锁定尽可能细粒度。跨度> 至于将它们加入一个函数中;我可以看到对于最小示例来说这将是一个好方法,但实际应用程序要复杂得多。此外,我还在显示线程中做其他事情。我从世界模型中提取必要的信息,并在显示线程处理 OpenGL 调用时释放锁。 最后,我还使用多线程模型作为一种抽象方式,它确实简化了我的设计。 @enobayram 相反,我相信一个功能(将任务委托给一些操作员,允许他们并行运行一些任务)非常适合更复杂的场景,而不是最小的场景。显然它需要仔细同步,但能够说:auto a = runA(); auto b = runB(); runC(); joinA(); joinB(); 很好地分解了整个复杂性。【参考方案5】:

我已经使用条件解决了这个问题,正如 Dale Wilson an FKaria 所建议的那样,但我在相反的方向使用它。因此,进程线程检查暂停标志,当它被设置时,它等待一个条件,从而释放锁。显示线程控制暂停标志,它还通过条件通知它来恢复进程线程。代码:(代码大体相同,我已经用//New标记了新行)

#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <iostream>

using namespace std;
using namespace boost;

mutex mut;
condition cond;

volatile bool shouldPause = false; //New

void process() 
        double start = time(0);
        while(1) 
                unique_lock<mutex> lock(mut);

                if(shouldPause) cond.wait(mut); //New

                this_thread::sleep(posix_time::milliseconds(10));
                std::cout<<".";
                if(time(0)>start+10) break;
        


int main() 

        thread t(process);

        while(!t.timed_join(posix_time::seconds(1))) 
                posix_time::ptime mst1 = posix_time::microsec_clock::local_time();
                cout<<endl<<"attempting to lock"<<endl;
                cout.flush();
                shouldPause = true; // New
                unique_lock<mutex> lock(mut);

                posix_time::ptime mst2 = posix_time::microsec_clock::local_time();
                posix_time::time_duration msdiff = mst2 - mst1;
                cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl;
                cout.flush();

                shouldPause = false; // New
                cond.notify_all(); // New
        


现在的输出正是我想要的:

...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 8
...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 8
...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 9
...................................................................................................
attempting to lock
.
acquired lock in: 8
...................................................................................................
attempting to lock
.
acquired lock in: 9
..........................

【讨论】:

以上是关于锁定可用时立即获取的主要内容,如果未能解决你的问题,请参考以下文章

如何处理当前不可用的选项?锁定与隐藏与错误消息

wordpress.com 和其他类似服务如何立即创建子域,即立即可用?

如何创建具有立即可用价值的 QFuture?

E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?

ASP.Net Core - 登录后如何等待用户身份立即可用?

Ubuntu16.04系统下 解决“无法获得锁 /var/lib/dpkg/lock -open (11:资源暂时不可用)无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?”(