6

编程小知识之 虚假唤醒(spurious wakeup)

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/103569108
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

编程小知识之 虚假唤醒(spurious wakeup)

tkokof1 2019-12-16 20:07:16 343
分类专栏: 语言 算法 随性 文章标签: 多线程

本文简单介绍了一些 虚假唤醒(spurious wakeup) 相关的知识
(注: 本文假设读者对多线程开发有一定了解)

高层次的多线程编程中,条件变量是个常见的同步方法,跟传统仅使用互斥量的方法相比,条件变量可以减少锁的竞争.

Pthread 举例,一个常见的条件变量的使用示例大概是这个样子的:

// flag for sync
bool g_signaled = false;
pthread_mutex_t g_mutex;
pthread_cond_t g_cond;

// wait method
void wait()
{ 
    pthread_mutex_lock(&g_mutex);
    
    while (!g_signaled)
    {      
        pthread_cond_wait(&g_cond, &g_mutex);
    }
      
    g_signaled = false;
    pthread_mutex_unlock(&g_mutex);  
}

// signal method
void signal()
{
    pthread_mutex_lock(&g_mutex);
    g_signaled = true;
    pthread_mutex_unlock(&g_mutex);  
    pthread_cond_signal(&g_cond);
}

代码中调用的 pthread_cond_wait 方法,作用在于可以让线程释放对应的互斥锁(g_mutex)并进入等待状态,然后在对应的条件变量(g_cond) signal 之后重新被唤醒并再次获取互斥锁.

上述示例代码中,我们在设置 g_signaled 之后调用了 pthread_cond_signal,正常来讲的话,之前调用 pthread_cond_wait 的线程会被唤醒,此时 g_signaled 应该一定为真,但是细心的朋友应该会发现,代码中我们却使用了一个循环来检查 g_signaled 的真值(并在发现 g_signaled 不为真时释放互斥锁然后重新进入了等待(通过重新调用 pthread_cond_wait)):

while (!g_signaled)
{      
    pthread_cond_wait(&g_cond, &g_mutex);
}

这么做的一个原因便是为了处理 虚假唤醒(spurious wakeup),所谓 虚假唤醒,指的是即便我们没有 signal 相关的条件变量(即没有调用 pthread_cond_signal),等待(调用了 pthread_cond_wait)的线程也可能被(虚假)唤醒,此时我们必须重新检查对应的标记值(以确认是否发生了(虚假)唤醒),又由于(虚假)唤醒可能会发生多次,所以我们最终需要使用循环来进行标记值检查.

虚假唤醒看上去很恼人,似乎我们应该在接口层消除这种现象(即让 pthread_cond_wait 不产生虚假唤醒),但有两个原因让虚假唤醒最终保留了下来:

1. 消除虚假唤醒非常困难

这里我们不对此做细节讲述,这里有些相关的讲解,有兴趣展开的朋友可以首先看看.

2. 即使消除了虚假唤醒,我们仍然需要循环检查标记值

这可能令人比较意外,问题在于除了虚假唤醒,还有一种称为 stolen wakeups 的现象也可能会影响标记值.

考虑下面的代码:

pthread_mutex_unlock(&g_mutex);
// gap here ...
pthread_cond_signal(&g_cond);

可以看到我们首先释放了互斥锁,接着 signal 了对应的条件变量,但是这两个操作之间是有"空隙"的,某一线程完全可以在这之间获取到互斥锁,改变标记值,然后再释放互斥锁,这导致标记值在 pthread_mutex_unlock 和 pthread_cond_signal 之间可能会发生变化,基于此,我们便仍然需要循环检查标记值(以防执行错误的逻辑).


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK