文章目录
  1. 可重入函数
  2. fork与互斥锁
  3. 线程与信号

可重入函数

Linux库函数只有一小部分是不可重入的,但Linux为其提供了对应的可重入版本,这些可重入版本的函数在原函数名尾部加_r;在多线程程序中调用库函数,必须使用其可重入版本。

fork与互斥锁

考虑下面情况:一个多线程程序的某个线程调用了fork函数,则子进程仅拥有一个执行线程,即调用fork的线程的完整复制,且子进程将自动继承父进程中互斥锁、POSIX信号量、条件变量的状态。即,父进程中被加锁的互斥锁在子进程中也是锁住的,但子进程并不清楚该互斥锁的状态,此时如果再次加锁,就会导致死锁。

为此,pthread专门提供了pthread_atfork函数以确保fork调用后父进程与子进程都清楚锁的状态。

1
2
3
4
5
6
7
8
/*
引用方式: #include <pthread.h>
prep: 在fork系统调用创建子进程之前被执行, 用来锁住所有父进程中的互斥锁
parent: 在fork系统调用创建子进程之后 & fork系统调用返回之前, 在父进程中执行, 释放所有在prep中被锁住的互斥锁
child: fork系统调用返回之前, 在子进程中被执行, 释放所有在prep中被锁住的互斥锁
返回0: 成功 || 返回错误码: 失败
*/
int pthread_atfork(void (* prep)(void), void (* parent)(void), void(* child)(void));

线程与信号

在多线程下应用程序应使用pthread版本的sigprocmask函数——pthread_sigmask。

1
2
3
4
5
/*
引用方式: #include <pthread.h>
返回0: 成功 || 返回错误码: 失败
*/
int pthread_sigmask(int how, const sigset_t * set);

函数的参数含义与sigprocmask的参数含义相同。

由于进程中所有线程共享进程的信号,因此线程库将根据线程掩码决定将信号发送到哪个具体的线程,但如果在每个子进程中都单独设置信号掩码,则很容易导致逻辑错误;此外,所有线程共享信号处理函数,当一个线程设置了某个信号的信号处理函数后,它将覆盖其他线程为同一信号设置的信号处理函数。

综上,应用程序应该定义一个专门的线程处理所有的信号。实现步骤如下。

  • 在主线程创建出其他子线程之前就调用pthread_sigmask来设置好信号掩码,所有新创建的子线程都将自动继承这个信号掩码;从而所有的线程都不会响应被屏蔽的信号;
  • 在某个线程中调用sigwait函数来等待信号并处理它;
1
2
3
4
5
6
7
/*
引用方式: #include <signal.h>
set: 要等待的信号的集合
sig: 存储函数返回的信号值
返回0: 成功 || 返回错误码: 失败
*/
int sigwait(const sigset_t * set, int * sig);

若sigwait正确返回,应用程序可对接收的信号作出处理;此时,应用程序无需再为信号设置信号处理函数。

最后,pthread提供了pthread_kill函数以将一个信号明确地发送给指定线程。

1
2
3
4
5
6
7
/*
引用方式: #include <signal.h>
thread: 指定目标线程
sig: 指定待发送的信号; sig为0则pthread_kill不发送任何信号, 但仍然执行错误检查, 可利用这种方式来检测目标线程是否存在
返回0: 成功 || 返回错误码: 失败
*/
int pthread_kill(pthread_t thread, int sig);