boost::interprocess::interprocess_condition::wait 在等待时不会原子地解锁互斥锁

Posted

技术标签:

【中文标题】boost::interprocess::interprocess_condition::wait 在等待时不会原子地解锁互斥锁【英文标题】:boost::interprocess::interprocess_condition::wait does not atomically unlock mutex while waiting 【发布时间】:2013-06-13 15:03:28 【问题描述】:

正如当前帖子标题所说,boost boost::interprocess::interprocess_condition::wait 假设在等待时原子地解锁互斥锁,但事实并非如此。

在以下代码中:

boost::interprocess::scoped_lock< boost::interprocess::interprocess_mutex > state_access_lock(impl->state->state_access_mut);
impl->state->state_access_cond.wait(state_access_lock);

在 VS2010 进入调试模式时,我按下了暂停键,当我看到 state_access_lock 在等待时仍然被锁定时,我感到很惊讶。

但这不是 boost 的文档所说的 here。

有人有什么建议吗?

谢谢。

【问题讨论】:

您对代码的行为有实际的观察还是只是调试器中某个变量的值? 我首先意识到了这种行为。因为我的第二个线程应该写然后调用通知,正在等待互斥锁被永久释放。只有在那之后,我才决定使用 VS 调试模式检查我的互斥锁到第一个线程中发生了什么。 除了这种根本性的破坏不太可能被忽视之外,各种条件变量的每个单独的代码路径(顺便说一下,你应该指定你正在使用哪个)都会经历解锁互斥锁。所以错误必须在其他地方。尝试发布更多上下文。 我在 boost::interprocess::interprocess_condition 的 wait 方法中跟踪执行代码,发现互斥锁本身通过调用 mut.unlock() 解锁,但 scoped_lock 本身仍然将 is_locked 设置为 true。所以我很困惑,如果我有这样的 scoped_lock:scoped_lock lk(mut) 调用 mut.unlock()lk.unlock() 有什么区别? 【参考方案1】:

好的,这是第一个线程:

void CSharedMemory::start(start_mode mode)

bool start_mut_locked = true;
impl->running = true;
impl->mode = mode;

stateMetaStruct* state = impl->proc_state;

boost::interprocess::scoped_lock< boost::interprocess::interprocess_mutex > state_access_lock(state->state_access_mut);

while(impl->running)

    state->data_written = false;
    while(!state->data_written)
    
        if(start_mut_locked)
        
            // We can now unlock and let other threads to send data.
            impl->start_mut.unlock();
            start_mut_locked = false;
        

        state->state_access_cond.wait(state_access_lock); // wait here upon sharedmemory's state change
        boost::interprocess::offset_ptr< stateMetaStruct > s = impl->shm_obj.find< stateMetaStruct >(boost::interprocess::unique_instance).first;
        state = s.get();

        if(!state->data_written)
        
            // Spurious wakeup.
            glm_debug("Spurious wakeup.");
        

        if(this == state->data_written_by_proccess)
        
            state->data_written = false;
            glm_debug("Ignoring my proper event.");
        
    

    if(impl->running)
    
        // Got action from other process.
        const interprocess_actions state_action = state->action;

        if(DO_STOP == state_action) 
        
        else if(DUMP_USERS_REQUEST == state_action) 
            impl->stateChangedListener->onDumpUsersRequest();
        
        else if(DUMP_USERS_REPLY == state_action) 
        
        else 
            glm_err("Unexpected state.");
        
    
   

第二个线程尝试使用此方法发送数据:

void CSharedMemory::sendDumpUsersRequest()

// Ensure shm is started.
boost::mutex::scoped_lock lk(impl->start_mut);

glm_debug("%s", __FUNCTION__);

boost::interprocess::offset_ptr< stateMetaStruct > s = impl->shm_obj.find< stateMetaStruct >(boost::interprocess::unique_instance).first;
stateMetaStruct* state = s.get();

boost::interprocess::scoped_lock< boost::interprocess::interprocess_mutex > state_access_lock(state->state_access_mut);

state->action = DUMP_USERS_REQUEST;

state->data_written = true;
state->data_written_by_proccess = this;

// Send request.
state->state_access_cond.notify_all();

行为是,第二个线程在尝试获取 scoped_mutex 时阻塞,因为第一个线程正在等待它。

【讨论】:

【参考方案2】:

根据目前的cmets,我想我可以推断出一个答案。

不要相信传递给 interprocess_condition::wait() 的 scoped_lock 的成员。 interprocess_condition 的合同(与 interprocess_condition_any 不同)规定您只能将其与 interprocess_mutex 的锁一起使用。知道这一点后,条件变量会将内部互斥锁从您的锁中拉出,以便比它对锁一无所知时更有效地完成工作。

因此,在解锁互斥锁时,它不会在您的 scoped_lock 上调用 unlock(),而是直接在互斥锁上调用。这对于内部实现来说很好;不要在家里这样做。如果在锁超出范围之前不重新锁定互斥锁,就会发生不好的事情。

换句话说,您在调试器中看到的行为并不表示存在问题。如果你有一个死锁,它一定是在别的地方。

编辑

给定的实际代码中的条件变量对我来说看起来不错。我发现与 start_mut 的交互有点奇怪。你确定那部分没有问题吗?

【讨论】:

好的,所以我应该写 state-&gt;state_access_mut.lock(); state-&gt;state_access_cond.do_wait(state-&gt;state_access_mut); 而不是这个 boost::interprocess::scoped_lock&lt; boost::interprocess::interprocess_mutex &gt; state_access_lock(impl-&gt;state-&gt;state_access_mut); impl-&gt;state-&gt;state_access_cond.wait(state_access_lock); 然后操纵 state_access_lock 它自己而不是把它放入 scoped_lock 对吗? 不,编写的代码实际上看起来不错。只是条件变量直接解锁了底层的互斥锁,而不是通过锁对象,所以你在调试器中看到了令人困惑的值。但这应该是 only 效果。如果你陷入僵局,你就会遇到不同的问题。为什么不发布两个线程的代码? 我使用“添加另一个答案”发布了代码,但代码超出了您今天的答案。 我使用 start_mut 来确保第一个线程在第二个线程调用 notify_all() 之前调用 wait。我这样做了,因为我的两个线程都是从主线程启动的,我无法知道第一个线程在第二个启动之前是否调用了wait()

以上是关于boost::interprocess::interprocess_condition::wait 在等待时不会原子地解锁互斥锁的主要内容,如果未能解决你的问题,请参考以下文章