多线程之间的同步

1. 多线程同步

1.1. 互斥锁pthread_mutex

1.1.1. 多线程程序中调用fork

  • 父进程使用fork()创建出的子进程会继承互斥锁的状态,所以如果父进程是多线程并且有一个线程锁住了mutex,则子进程创建时也会锁住(两个互斥锁分属于两个进程),此时子进程若调用pthread_mutex_lock则会导致死锁
  • 我们可以在调用fork()之前使用pthread_atfork(prepare, parent, child)来保证父子进程中的互斥锁都有一个清晰的状态
    • prepare,parent,child都为函数指针,prepare在fork调用之后,子进程创建之前使用,parent和child分别在fork调用返回之前,在父进程和子进程中使用,我们可以在prepare中对mutex上锁,在parent和child分别对其解锁来达到fork之后父子进程中的互斥锁都为解锁的

条件变量pthread_cond

  • mutex用于同步线程对于共享数据的访问,conditional_variable用于同步线程之间共享数据的值
  • pthread_cond_signal不会解锁mutex(它不能,因为它没有对mutex的引用,所以它怎么知道要解锁什么?)事实上,发出信号的线程不需要与互斥体有任何连接, pthread_cond_wait在将等待线程放入等待队列之前先将mutex加锁,放入之后解锁,当线程被成功唤醒(即wait函数返回时),mutex会再次被锁上,所以在pthread_cond_wait在被调用之后到调用线程被成功放入等待队列这段时间内,应该确保pthread_cond_signal和pthread_cond_broadcast等函数不会修改条件变量的值,将信号函数加上锁是个很好的方法,所以条件变量的通常用法如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    thread 1:
    pthread_mutex_lock(&mutex);
    while (!condition) //等待某个条件变真,while循环防止假唤醒,cond通常被设为能够表示目标线程的唯一标识,从而唤醒一个指定线程
    pthread_cond_wait(&cond, &mutex);
    /* do something that requires holding the mutex and condition is true */
    pthread_mutex_unlock(&mutex);

    thread2:
    pthread_mutex_lock(&mutex);
    /* do something that might make condition true */
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

1.2. 多线程信号处理

  • fork创建子进程,子进程会继承父进程的信号掩码并清除信号处理函数
  • 对于线程来说,使用pthread_sigmask可以设置各线程的信号掩码,使用pthread_create创建的新线程也会继承该信号掩码
  • 所有线程都共享信号处理函数,一个线程针对某信号设置的信号处理函数会覆盖其余线程的信号处理函数
  • 当信号被产生(generate)后,内核会查找进程中没有为此信号设置掩码的线程,若没有,则会将该信号挂起(pending),若有漏网之鱼,信号被成功投递(delivered)则信号处理函数被触发,若没有为此信号设置处理函数,则会触发进程的默认行为
  • sigwait(set, signal)会等待信号集中的信号,知道发现某信号处于pending状态,这说明sigwait可以处理被信号掩码屏蔽的信号,它可以取出处于进程挂起信号集(sigpending()函数获取到的信号集)内的信号,之后实现对于信号的处理,这种处理方式是线程级别的,和signal或sigaction所设置的线程共享处理函数的方式不同,二者只能存在一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void* sig_handler(void* arg) {
sigset_t * set = (sigset_t*)arg;
/* sigset_t set1;
sigemptyset(&set1);
sigaddset(&set1, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &set1, nullptr); */
int s, sig;
for (; ; ) {
s = sigwait(set, &sig);
if(s != 0) {
handle_errno_en(s, "sigwait");
printf("signal handling thread got signal %d\n", sig);
}
sleep(5);
printf("got a signal\n");
//break;
}
}

int main(int argc, char* argv[]) {
pthread_t thread;
sigset_t set;
int s;
sigemptyset(&set);
sigaddset(&set, SIGQUIT);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
pthread_create(&thread, nullptr, sig_handler, &set);
pause();
return 0;
}

例程取自pthread_sigmask的man手册,它展示了如何在一个线程内实现对于信号的处理

在多线程环境中,您可以使用sigwait UNIX系统调用来同步地处理异步信号。这可以帮助您的应用程序避免中断,并使其行为更加可预测。通常,专用的信号处理程序线程使用sigwait等待异步信号。为sigwait提供的信号掩码表示要等待哪些信号。在发送信号时,sigwait返回信号编号,信号处理程序线程执行信号处理程序。要通过sigwait接收异步信号,进程必须:
创建一个专用的信号处理程序线程,在该线程中调用sigwait来捕获异步信号。为这个线程屏蔽这些信号。对于所有其他线程,将set参数中指定的信号屏蔽为sigwait。


多线程之间的同步
http://example.com/2023/11/19/MultithreadSync/
作者
李凯华
发布于
2023年11月19日
许可协议