sem_init重复调用引发sem_wait线程无法被唤醒

问题

一段老代码,两个线程,一个线程调用sem_wait等待信号量,另外一个线程在某失败分支会调用sem_init清信号量,结果导致sem_wait线程无法被唤醒;

分析

Linux manpage

从描述中可见,初始化一个已经被初始化的信号量会导致未定义行为;

 1 NAME
 2        sem_init - initialize an unnamed semaphore
 3 
 4 SYNOPSIS
 5        #include <semaphore.h>
 6 
 7        int sem_init(sem_t *sem, int pshared, unsigned int value);
 8 
 9        Link with -lrt or -pthread.
10 
11 DESCRIPTION
12 ...
13 
14        Initializing a semaphore that has already been initialized results in undefined behavior.

glibc源码

到底会发生什么未定义行为,我们直接看源码吧;

首先,对比结构体,旧结构体只有value成员,新结构体中增加了private和nwaiters成员;nwaiters成员会在调用sem_wait时候增加;

 

然后,对比sem_post唤醒函数;可见,新唤醒函数会在唤醒操作执行之前对nwaiters进行判断,只有当nwaiters>0时,才进行唤醒;

 

而旧的唤醒操作,则没有类似判断;

 

现在,我们清楚了,老代码用的老版本的glibc,内部没有等待判断,一直没有出问题,而使用新版本的glibc之后,加入了判断,就有问题了;

结论

  1. sem_init是用来在初始化的时候调用初始化信号量的,并不是用来将信号量清零的;
  2. 重复调用sem_init的行为可能导致已经处于sem_wait的线程无法被唤醒;
  3. 旧版本的glibc机制比较弱,所以老代码一直运行很好;但是新glibc作了检查,所以会出问题;
  4. 按照目前代码看,如果单个线程自己在调用了sem_wait之后再调用sem_init时没什么影响的;但是不保证以后的glibc会再做什么修改造成影响;
  5. 除了初始化阶段,其他流程中不要使用sem_init;
  6. 最好使用其他方式替代信号量,比如条件变量;
posted @ 2019-10-30 19:41  AlexAlex  阅读(1555)  评论(0编辑  收藏  举报