《POSIX多线程程序设计》——David R.Buten
Pthreads改变了很多POSIX进程函数的含义。
fork
应尽量避免在一个多线程的程序中使用fork。。
当多线程进程调用fork创造子进程时,Pthreads指定只有那个调用fork的线程在子进程内存在。子进程拥有与父进程相同的互斥量、线程私有数据键等。fork调用不会影响互斥量的状态。如果它在父进程中被锁住,则它在子进程中被锁!
exec
对exec的调用,将很快终止进程内除调用exec的线程外的所有线程。它们不执行清除处理器或线程私有数据destructors。使用此函数,你应该解锁当前进程可能锁住的任何pshared互斥量。
exit
exit函数提供了一个简单的方法停止整个进程。当程序某种程度上被破坏时,试图干净地停止应用线程可能是危险的。在这种情况下,可以调用exit来很快地结束所有的处理。
stdio
Pthreads要求ANSI C标准I/O(stdio)函数时线程安全的。因为stdio包需要为输出缓冲区和文件状态指定静态存储区,stdio实现将使用互斥量和信号灯等同步机制。
flockfile和funlockfile
有时保证一系列stdio操作以不被中断的顺序执行是重要的。例如,一个提示符后面跟着一个从终端的读操作,或者一起在输出文件出现的两个写操作。因此,Pthreads增加了锁住一个文件的机制并且规定文件锁如何与stdio内部锁交互。
(例子:向stdout写提示符号并从stdin读响应)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void* prompt_routine (void *arg) {
char *prompt = (char *)arg;
char *string;
int len;
string = (char *)malloc(128);
flockfile(stdin);
flockfile(stdout); // Pthreads推荐总是在输出流之前锁住输入流
printf("%s", prompt);
if (fgets(string, 128, stdin) == NULL)
string[0] = '\0';
else {
len = strlen(string);
if (len > 0 && string[len-1] == '\n')
string[len-1] = '\0';
}
funlockfile(stdout); // 考虑一致的锁层次,先解锁输出流后解锁输入流
funlockfile(stdin);
return (void *)string;
}
int main(int argc, char *argv[]) {
pthread_t thread1, thread2, thread3;
char *string;
pthread_create(&thread1, NULL, prompt_routine, "Thread 1> ");
pthread_create(&thread2, NULL, prompt_routine, "Thread 2> ");
pthread_create(&thread3, NULL, prompt_routine, "Thread 3> ");
pthread_join(thread1, (void**)&string);
printf("Thread 1: \"%s\"\n", string);
free(string);
pthread_join(thread2, (void**)&string);
printf("Thread 1: \"%s\"\n", string);
free(string);
pthread_join(thread3, (void**)&string);
printf("Thread 1: \"%s\"\n", string);
free(string);
return 0;
}
结果:
Thread 1> 12
Thread 2> 34a
Thread 1: "12"
Thread 1: "34a"
Thread 3> abcd 23
Thread 1: "abcd 23"
getchar_unlocked和putchar_unlocked
ANSI C提供了向stdio缓冲区高效地读取和写入单个字符的函数(宏实现)。Pthreads增加了新函数来代替旧的高效宏,这些函数本质上与传统的宏实现一样,并且不执行任何锁操作,但可以和文件锁一起使用,用于满足Pthreads要求的锁住stdio流数据来防止代码对stdio缓冲区的偶然破坏。
#include <pthread.h>
#include <stdio.h>
void* lock_routine(void *arg) {
char *pointer;
flockfile(stdout);
for (pointer = arg; *pointer != '\0'; pointer++) {
putchar_unlocked(*pointer);
sleep(1);
}
funlockfile(stdout);
return NULL;
}
void* unlock_routine(void *arg) {
char *pointer;
for (pointer = arg; *pointer != '\0'; pointer++) {
putchar(*pointer);
sleep(1);
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t thread1, thread2, thread3;
int flock_flag = 1;
void* (*thread_func)(void *);
if (argc > 1)
flock_flag = atoi (argv[1]);
if (flock_flag)
thread_func = lock_routine;
else
thread_func = unlock_routine;
pthread_create(&thread1, NULL, thread_func, "this is thread 1\n");
pthread_create(&thread2, NULL, thread_func, "this is thread 2\n");
pthread_create(&thread3, NULL, thread_func, "this is thread 3\n");
pthread_exit(NULL);
return 0;
}
结果:
线程安全的函数
Pthreads定义了现存函数的线程安全的变体,它们在相应函数名结尾处添加后缀_r。
- 用户和终端ID
getlogin_r、ctermid、ttyname_r - 目录搜索
readdir_r(两个线程并行搜索同一目录,必须同步传给readdir_r
函数的共享struct dirent
的使用) - 字符串
token - 时间表示
asctime_r、ctime_r、gmtime_r、localtime_r - 随机数
read_r - 组和用户数据库
getgrgid_r、getgrnam_r、getpwuid_r、getpwnam_r
信号
- 避免与线程一起使用信号总是最好的,但将它们分开经常又是不可能或不实际的。
- 当多线程工作时,向进程内任何线程传送一个SIGKILL信号将终止进程。传送一个SIGSTOP信号将导致所有的线程停止直到收到SIGCONT信号。
- 每个线程有自己私有的信号掩码,可通过
pthread_sigmask
来修改。当一个线程被创建时,它继承了创造它的线程的信号掩码。(如果想在所有地方屏蔽一个信号,则首先在主线程中屏蔽它) - 在一个进程内,一个线程可调用
pthread_kill
想一个特定线程(包括自己)发信号。如果线程没有将此信号屏蔽,并且没在sigwait中阻塞,此信号的行为将被执行。对于多线程而言,Pthreads指定raise(SIGABRT)
与pthread_kill(pthread_self(), SIGABRT)
一样。 - Pthreads增加了允许线程代码同步地处理异步信号的函数(sigwait类似函数)。在多线程代码中总是使用sigwait与异步信号一起工作。调用sigwait等待的信号必须在调用线程中被屏蔽,而且通常应该在所有的线程中被屏蔽。如果两个线程在sigwait上阻塞,只有一个线程将收到送给进程的信号。
- sigwait作为一个Pthreads函数,通过返回一个错误数字来报告错误;而它的兄弟函数sigwaitinfo和sigtimedwait在Pthreads以前就被加到POSIX中,它们使用旧的errno机制。sigtimedwait如果在指定的间隔内没有收到选定信号,则返回EAGAIN错误。
- 在POSIX.1b中,Pthreads增加了被称为
SIGEV_THREAD
的新通知机制。
(范例:sigwait处理SIGINT信号(Ctrl+c))
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int interrupted = 0;
sigset_t signal_set; // 信号集
void* signal_waiter(void *arg) {
int sig_number;
int signal_count = 0;
while (1) {
sigwait(&signal_set, &sig_number); // 同步处理异步信号
if (sig_number == SIGINT) {
printf("Got SIGINT (%d of 5)\n", signal_count+1);
if (++signal_count >= 5) {
pthread_mutex_lock(&mutex);
interrupted = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
break;
}
}
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t signal_thread_id;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGINT);
pthread_sigmask(SIG_BLOCK, &signal_set, NULL); // 屏蔽一些信号(SIGINT)(必须在sigwait调用前)
pthread_create(&signal_thread_id, NULL, signal_waiter, NULL); // 线程会从创造者那儿继承起始信号掩码
pthread_mutex_lock(&mutex);
while (!interrupted)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("Main terminating with SIGINT\n");
return 0;
}
结果:
^CGot SIGINT (1 of 5)
^CGot SIGINT (2 of 5)
^CGot SIGINT (3 of 5)
^CGot SIGINT (4 of 5)
^CGot SIGINT (5 of 5)
Main terminating with SIGINT
(范例:SIGEV_THREAD信号像线程一样处理计时器消息)
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
timer_t timer_id;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int counter = 0;
void timer_thread(union sigval arg) {
pthread_mutex_lock(&mutex);
if (++counter >= 5)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
printf("Timer %d\n", counter);
}
int main(int argc, char *argv[]) {
struct itimerspec ts;
struct sigevent se;
se.sigev_notify = SIGEV_THREAD; // Pthreads增加了此通知机制,使信号通知函数像线程起始函数一样运行
se.sigev_value.sival_ptr = &timer_id;
se.sigev_notify_function = timer_thread; // 线程起始函数
se.sigev_notify_attributes = NULL; // 线程属性(默认PTHREAD_CREATE_DETACHED)
ts.it_value.tv_sec = 5;
ts.it_value.tv_nsec = 0;
ts.it_interval.tv_sec = 5;
ts.it_interval.tv_nsec = 0;
printf("Creating timer\n");
timer_create(CLOCK_REALTIME, &se, &timer_id); // 创建一个进程范围内的定时器
printf("Setting timer %d for 5-second expiration...\n", (int)timer_id);
timer_settime(timer_id, 0, &ts, 0); // 每次定时器到期时都会调用timer_thread
pthread_mutex_lock(&mutex);
while (counter < 5)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
return 0;
}
结果:
Creating timer
Setting timer 163066008 for 5-second expiration...
Timer 1
Timer 2
Timer 3
Timer 4
Timer 5
信号灯
如果你的系统支持POSIX信号灯,在<unistd.h>
中定义了_POSIX_SEMAPHORES
选项。
“锁”信号灯——创建一个初始值为1的信号灯,允许一个线程不必阻塞就完成sem_wait操作。
“等待”信号灯——创建一个初始值为0的信号灯,调用sem_wait的线程阻塞直到其他线程调用sem_post。
(范例:“等待”信号灯的使用)
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
sem_t semaphore;
void *sem_waiter (void *arg) {
long num = (long)arg;
printf ("Thread %ld waiting\n", num);
sem_wait(&semaphore); // sem > 0 : sem减1后立即返回 : 阻塞
printf("Thread %ld resuming\n", num);
return NULL;
}
int main(int argc, char *argv[]) {
int thread_count;
pthread_t sem_waiters[5];
sem_init(&semaphore, 0, 0);
for (thread_count = 0; thread_count < 5; thread_count++)
pthread_create(&sem_waiters[thread_count], NULL, sem_waiter, (void *)thread_count);
sleep(2);
while (1) {
int sem_value;
sem_getvalue(&semaphore, &sem_value);
if (sem_value >= 5) // 5个线程阻塞等待,唤醒5次
break;
printf("Posting from main: %d\n", sem_value);
sem_post(&semaphore); // sem++
}
for (thread_count = 0; thread_count < 5; thread_count++)
pthread_join (sem_waiters[thread_count], NULL);
return 0;
}
结果:
Thread 2 waiting
Thread 0 waiting
Thread 1 waiting
Thread 3 waiting
Thread 4 waiting
Posting from main: 0
Posting from main: 1
Posting from main: 2
Posting from main: 1
Thread 3 resuming
Thread 2 resuming
Thread 0 resuming
Posting from main: 0
Posting from main: 1
Posting from main: 2
Posting from main: 3
Posting from main: 3
Posting from main: 4
Thread 4 resuming
Thread 1 resuming