2.线程同步 POSIX支持用于短期锁定的互斥锁以及可以等待无限期限的条件变量。 在线程化程序中进行信号处理格外复杂,但是用专用线程来取代信号处理程序,可以降低其复杂性。 学习目标:互斥锁、条件变量、读--写锁、经典同步问题、带信号的线程 2.1POSIX同步函数 描 述 POSIX 函数 互斥锁 pthread_mutex_t pthread_mutex_destroy pthread_mutex_init pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock 条件变量 pthread_cond_destroy pthread_cond_init pthread_cond_broadcast pthread_cond_signal pthread_cond_timewait pthread_cond_wait 读--写锁 pthread_rwlock_destroy pthread_rwlock_init pthread_rwlock_rdlock pthread_rwlock_wrlock pthread_rwlock_timedrdlock pthread_rwlock_timewrlock pthread_rwlock_tryrdlock pthread_rwlock_trywrlock
每种同步机制都提供了一个初始化函数和一个销毁对象的函数; 互斥锁和条件变量可以进行静态初始化; 每种同步机制都有与之相应的属性对象,也可使用默认的属性对象。 2.2互斥锁(或互斥量) 互斥量可以处于锁定状态,也可以处于解锁状态。 互斥量有一个等待该互斥量的线程队列,互斥量的等待队列中的线程顺序由调度策略确定。 互斥量只能短时间持有,等待输入这样的持续时间不确定的情况,用条件变量来同步。 互斥函数不是线程取消点,也不能被信号函数中断,除非线程终止或异步取消了线程。 2.2.1创建并初始化一个互斥量 POSIX用pthread_mutex_t类型表示互斥锁,程序在使用其同步之前必须对其初始化。 如果线程试图初始化一个已经被初始化的互斥量,POSIX中明确指出,该行为是为定义的,必须避免出现这种情况。 对静态分配的pthread_mutex_t只要将PTHREAD_MUTEX_INITIALIZER赋给变量就行了。 例: pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; 静态初始化程序比pthread_mutex_init更有效。 对动态分配或者没有默认互斥属性的pthread_mutex_t,要调用pthread_mutex_init初始化 #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); attr为NULL则初始化一个带默认属性的互斥量。 成功返回0,不成功返回非零的错误码: EAGAIN 系统缺乏初始化*mutex所需的非内存资源 ENOMEN 系统缺乏初始化*mutex所需的内存资源 EPERM 调用程序没有适当的优先级 例: int error; pthread_mutex_t mylock; if ( (error = pthread_mutex_init(&mylock, NULL)) != 0 ) { fprintf (stderr, “Failed to initialize mylock : %s/n”, strerror(error) ); exit(1); } 2.2.2销毁一个互斥量 #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); 成功返回0,不成功返回非零的错误码,没有定义必须检测的错误。 例: int error pthread_mutex_t mylock; if ( (error = pthread_mutex_destroy(&mylock)) != 0 ) { fprintf (stderr, “Failed to destroy mylock : %s/n”, strerror(error)); exit(1); } pthread_mutex_init可以对已经被pthread_mutex_destroy销毁的变量进行重新初始化。 POSIX明确说明,以下两种情况的行为是为定义的,须避免: 线程引用已经销毁的互斥量;一个线程调用了pthread_mutex_destroy,而另一个将互斥量锁定。 2.2.3互斥量的锁定和解锁 #include <pthread.h> int pthread_mutex_lock( pthread_mutex_t *mutex); int pthread_mutex_trylock( pthread_mutex_t *mutex); int ptrehad_mutex_unlock( pthread_mtex_t *mutex); 成功返回0,不成功返回非零的错误码,这三个函数必须检测相应的错误,以下是错误码: EINVAL 互斥量具有协议属性PTHREAD_PRIO_PROTECT(该属性可以防止排序优先级反转的情况),而调用程序的优先级比互斥量当前的优先级的上限还要高(pthread_mutex_lock pthread_mutex_trylock)。 EBUSY 另一个线程持有锁(pthread_mutex_trylock)。 例: pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mylock); /* critical section */ pthread_mutex_unlock(&mylock); //代码省略了错误检测 2.2.4用互斥量保护不安全的库函数 可以使用互斥量保护不安全的库函数,POSIX标准将C库中的rand函数列为多线程化应用程序不安全的函数,如果能确保不会有两个函数同时调用它,就可以在一个多线程化的环境中使用rand函数。以下提供安全的例程 例: #include <pthread.h> #include <stdlib.h> int randsafe(double *ranp) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int error; if ( (error = pthread_mutex_lock(&lock)) != 0 ) { return error; } *ranp = ( rand() + 0.5 )/(RAND_MAX + 1.0 ); return pthread_mutex_unlock(&lock); } 2.2.5用互斥量对标志符和全局值同步 #include <pthread.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <stdio.h> #include <unistd.h> #define TEN_MILLION 1000000L static int doneflag = 0; static pthread_mutex_t donelock = PTHREAD_MUTEX_INITIALIZER; static int globalerror = 0; static pthread_mutex_t errorlock = PTHREAD_MUTEX_INITIALIZER; static int count = 0; static double sum = 0.0; static pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; int randsafe(double *ranp) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int error; if ( (error = pthread_mutex_lock(&lock)) != 0 ) { return error; } *ranp = (rand() + 0.5)/(RAND_MAX + 1.0); return pthread_mutex_unlock(&lock); } int getdone(int *flag) { int error; if ( (error = pthread_mutex_lock(&donelock)) != 0 ) { return error; } *flag = doneflag; return pthread_mutex_unlock(&donelock); } int setdone(void) { int error; if ( (error = pthread_mutex_lock(&donelock)) != 0 ) { return error; } doneflag = 1; return pthread_mutex_unlock(&donelock); } int geterror(int *error) { int terror; if ( (terror = pthread_mutex_lock(&errorlock)) != 0 ) { return terror; } *error = globalerror; return pthread_mutex_unlock(&errorlock); } int seterror(int error) { int terror; if (error == 0) { return error; } if ( (terror = pthread_mutex_lock(&errorlock)) != 0 ) { return terror; } if ( globalerror == 0 ) { globalerror = error; } terror = pthread_mutex_unlock(&errorlock); return terror>0 ? terror:error; } int add(double x) { int error; if ( (error = pthread_mutex_lock(&sumlock)) != 0 ) { return seterror(error); } sum += x; count ++; error = pthread_mutex_unlock(&sumlock); return seterror(error); } int getsum(double *sump) { int error; if ( (error = pthread_mutex_lock(&sumlock)) != 0 ) { return seterror(error); } *sump = sum; error = pthread_mutex_unlock(&sumlock); return seterror(error); } int getcountandsum(int *countp, double *sump) { int error; if ( (error = pthread_mutex_lock(&sumlock)) != 0 ) { return seterror(error); } *countp = count; *sump = sum; error = pthread_mutex_unlock(&sumlock); return seterror(error); } int showresults(void); void *computethread(void *arg1) { int error; int localdone = 0; struct timespec sleeptime; double val; sleeptime.tv_sec = 0; sleeptime.tv_nsec = TEN_MILLION; while (localdone == 0) { if ( (error = randsafe(&val)) != 0 ) { break; } if ( (error = add(sin(val))) != 0 ) { break; } if ( (error = getdone(&localdone)) != 0 ) { break; } nanosleep(&sleeptime, NULL); } seterror(error); return NULL; } int main(int argc, char *argv[]) { int error; int i; int numthreads; int sleeptime; pthread_t *tids; if (argc != 3) { fprintf (stderr, "Usage: %s numthreads sleeptimes/n", argv[0]); exit(1); } numthreads = atoi (argv[1]); sleeptime = atoi (argv[2]); if ( (tids = (pthread_t *)calloc (numthreads, sizeof(pthread_t))) == NULL ) { perror("Failed to allocate space for thead IDs"); exit(2); } for (i=0; i<numthreads; i++) { if ( (error = pthread_create (tids+i, NULL, computethread, NULL)) != 0) { fprintf (stderr, "Failedt to start thread %d:%s/n", i, strerror(error)); exit(3); } } sleep (sleeptime); if ( (error = setdone()) != 0 ) { fprintf (stderr, "Failed to set done:%s/n", strerror(error)); exit(4); } for (i=0; i<numthreads; i++) { if ( (error = pthread_join(tids[i], NULL)) != 0 ) { fprintf (stderr, "Failed to join thread %d:%s/n", i, strerror(error)); exit(5); } } if ( showresults() != 0 ) { exit(6); } exit(0); } int showresults(void) { double average; double calculated; int count; double err; int error; int gerror; double perr; double sum; if ( ((error = getcountandsum(&count, &sum)) != 0) || ((error = geterror(&gerror)) != 0) ) { fprintf (stderr, "Failed to get results: %s/n", strerror(error)); return -1; } if (gerror != 0) { fprintf (stderr, "Failet to compute sum:%s/n", strerror(gerror)); return -1; } if (count == 0) { printf ("NO value were summed./n"); } else { calculated = 1.0 -cos(1.0); average = sum/count; err = average - calculated; perr = 100.0*err/calculated; printf ("The sum is %f and the count is %d/n", sum, count); printf ("The average is %f and error is %f or %f%%/n", average, err, perr); } return 0; } ./mutex 2 1 The sum is 121.423602 and the count is 254 The average is 0.478046 and error is 0.018348 or 3.991315% 2.2.6用互斥量使数据结构成为线程安全的 线程化程序中大多数共享的数据结构都必须由同步机制保护,以确保能得到正确的结果。 实现只需要将每个函数都包装在一对互斥调用中。 2.3最多一次和最少一次的执行 最多一次和最少一次,对初始化来说非常重要。换句话说,初始化的工作正好执行一次。有时程序结构保证最少执行一次,这是时就需要结合最多一次的策略来保证正好执行一次。 以下讨论最多执行一次常用的策略: 单次初始化的概念非常重要,例如为一个已经初始化的互斥量调用pthread_mutex_init所产生的效果是为定义的。 所以,POSIX提供了pthread_once函数确保这个语义的实现。 #include <pthread.h> int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); //动态初始化 函数成功返回0,不成功返回非零的错误码,没有定义必须检测的错误类型 pthread_once_t once_control = PTHREAD_ONCE_INIT; //静态初始化 例1:最多执行一次 用pthread_once来初始化一个变量,并且打印出一条语句的函数 #include <pthread.h> #include <stdio.h> static pthread_once_t initonce = PTHREAD_ONCE_INIT; int var; static void initialization(void) { var = 1; printf(“The variable was initialized to %d/n”, var); } int printfinitonce(void) { return pthread_once(&initonce, initialization); } 函数initialization最多执行一次 例2:最多执行一次 #include <pthread.h> #include <stdio.h〉 int printinitmutex(int *var, int value) { int error; static int done = 0; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; if ( (error = pthread_mutex_lock(&lock)) != 0 ) { return error; } if ( done == 0 ) { *var = value; printf (“The variable was initialized to %d/n”, value); done = 1; } return pthread_mutex_unlock(&lock); } 讨论1:如果去除done和lock的static限定符会发生如下结果 结果:在一个块的内部作用于变量的static限定符确保了他们在块的后继执行过程中始终存在。若果没有static修饰,done和lock就变成了自动变量。在这种情况下,每个队printinitmutex的调用都会分配新的变量,而每次return都会释放掉这些变量,函数将不再工作。 讨论2:将done和lock的申明放到printinitmutex函数外,结果如下 结果:函数printinitmutex可以正常工作。但是,定义在同一个文件中的其他函数就可以访问done和lock了。将他们保持在函数内部可以更安全的保证”最多一次” 2.4条件变量 条件变量是线程可以使用的另一种同步机。条件变量给多个线程一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。 考虑如下问题: 假设两个变量x和y被多个线程共享。我们希望线程一直等到x和y相等为止。 典型的不正确的忙等解决方法是:while(x != y); 根据线程的调度方式,执行忙等的线程可能永远都不会让其他的线程使用cpu,这样x和yde 值永远也不会变了。同时对共享变量的访问也应该被保护起来。 等待断言x==y 为真的正确的非忙等策略: a.锁定互斥量 b.测试条件x==y c.如果为真,解除对互斥量的锁定,并推出循环 d.如果为假,将线程挂起,并解除对互斥量的锁定 问题在于:如何保证解除互斥量的锁定和线程挂起之间x和y没有发生改变。 解决办法:解除锁定和挂起必须是原子操作(pthread_cond_wait)。 2.4.1创建和销毁条件变量 POSIX用pthread_cond_t类型的变量来表示条件变量。 将NULL传给attr将用默认属性初始化一个条件变量 创建: #include <pthread.h> int pthread_cond_init(pthread_cond_t cond, const pthread_condattr_t *restrict attr); //动态初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化 函数pthread_cond_init成功返回0,不成功返回非零的错误码: EAGAIN 系统缺乏初始化*cond所需要的非内存资源 ENOMEM 系统缺乏初始化*cond所需要的内存资源 销毁: #include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); 成功返回0,不成功返回非零的错误码,没有定义必须检测的错误码。 试图初始化一个已经被初始化了的条件变量;引用一个已经被销毁的条件变量;销毁一个使其他线程阻塞了的条件变量,这些行为都是为定义的。尽量避免 2.4.2等待和通知条件变量 条件变量是和断言(条件测试)一起调用的。条件变量的名字就是同这个断言相关联的。 通常线程会对一个条件进行测试,测试失败,就调用pthread_cond_wait或pthread_cond_timewait。 函数中cond指向条件变量,mutex指向互斥量,线程在调用之前应该拥有这个互斥量。当线程被放在条件变量等待队列中是,等待队列会使线程释放这个互斥量。 函数pthread_cond_timewait的第三个参数是一个指向返回时间的指针,这个时间是绝对时间而不是时间间隔。 等待: #include <pthread.h> int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 成功返回0,不成功返回非零的错误码;abstime指定的时间到期,则返回ETIMEOUT。 例:使线程(非忙)等待,直到a大于或等于b为止(清楚起见省略了错误检测) static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //变量类型必须是静态 pthread_mutex_lock(&mutex); //调用pthread_cond_wait之前线程必须拥有互斥量 while(a < b) //pthread_cond_wait返回之后必须再次检测条件是否满足 { pthread_cond_wait(&cond, &mutex); //原子的释放互斥量,并阻塞 } pthread_mutex_unlock(&mutex); pthread_cond_wait返回有可能是假唤醒,因为只要条件测试中涉及的变量改变就会通知条件变量的等待队列,函数返回只能说明变量有改变,必须重新进行测试,不满足则继续阻塞。 pthread_cond_wait只是提供解锁和阻塞线程的原子操作,并提供线程排队功能,当条件改变时对互斥量加锁返回。条件变量可以看成是队列的名称(自己定义的) 通知: 当一个线程改变了可能会使断言成真的变量时,他应该唤醒一个或多个在等待断言成真的线程。 pthread_cond_signal函数至少解除了一个阻塞在cond指向的条件变量上的线程的阻塞。 pthread_cond_broadcast函数解除所有阻塞在cond指向的条件变量上的线程的阻塞。 但是,这两个函数只是解除线程在条件变量上的阻塞,并没有解除互斥量上的阻塞。 #include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); 一些使用条件变量必须遵守的规则: a.测试断言之前获得互斥量。 b. pthread_cond_wait返回之后重新对断言进行测试。 因为返回可能是由某些不相关的事件或无法使断言成真的pthread_cond_signal引起的, c.在修改断言中出现的任一互斥量之前,要获得互斥量。 d.通常测试断言或者修改共享变量的应用中,使用互斥量这种短时间锁定的策略 e.pthread_mutex_unlock显示地释放互斥量, pthread_cond_wait隐式地释放互斥量。 2.5读写锁 一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁, 而互斥量只能同时有一个线程加锁。 当读写锁是写加锁状态时,在解锁之前试图对这个锁加锁的线程都会被阻塞。 当读写锁是读加锁状态时,所有以读模式对他进行加锁的线程都可以得到访问权;所有希望以写模式对此锁进行加锁的线程,必须阻塞直到所有的线程释放读锁(这种情况下读写锁通常会阻塞随后的读模式锁请求,避免读模式锁长期占用,而等待的写模式所请求得不到满足)。 2.5.1创建和销毁 与互斥量一样pthread_rwlock_t 表示读写锁 创建: #include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); attr为NULL时,以默认属性创建读写锁 成功返回0,不成功返回非零的错误码,函数必须检测的错误码: EAGAIN 系统缺乏初始化*rwlock所需的非内存资源 ENOMEM 系统缺乏初始化*rwlock所需的内存资源 EPERM 调用程序没有适当的权限 销毁: #include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock *rwlock); 成功返回0,不成功就返回一个错误码,没有定义必须检测的错误。 试图初始化一个已经初始化了的读写锁,引用一个已经销毁的读写锁,结果都是未定义的。 2.5.2加锁和解锁 pthread_rwlock_rdlock和pthread_rwlock_wrlock函数一直阻塞,直到锁可用为止; pthread_rwlock_tryrdlock和pthread_rwlock_trywrlock函数会立即返回(EBUSY)。 #include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_ t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 成功返回0,不成功返回非零的错误码; 如果锁已经被持有,而无法获取,pthread_rwlock_tryrdlock和pthread_rwlock_trywrlock返回EBUSY。 情况1:如果线程在一个已经用pthread_rwlock_wrlock获取的锁上调用pthread_rwlock_rdlock 结论:POSIX申明,有可能会出现死锁 情况2:如果线程在一个已经用pthread_rwlock_rdlock获取的锁上调用pthread_rwlock_rdlock 结论:线程会持有同一个读写锁上的多个并发的读锁。应该确保解除锁定调用的数目和锁定调用的数目项匹配,以释放锁定。 2.5.3读写锁和互斥锁的比较 互斥锁是一种低开销的同步机制,如果程序中函数只需要在很短的一段时间持有锁,那么互斥锁是相对高效的; 读写锁有一些开销,所以,当实际的读操作要花费相当长的时间的时候(如访问磁盘引起的读操作),他们的优点就显现出来了。在这种情况下,严格的线性执行效率是很低的。 2.6线程与信号处理 进程中所有线程都共享进程的信号处理函数,但每个线程都有它自己的信号掩码。 如果有几个线程都解除了对同一个异步信号的阻塞,系统就从中挑选一个来处理信号。 线程中信号传递的类型 异步:传递给某些解除了对该信号的阻塞的线程 同步:传递给引发(该信号)的线程 定向:传递给标识了的线程(pthread_kill) 2.6.1将信号定向到一个特定的线程中 pthread_kill 函数产生信号码为sig的信号,并将其传送到thread指定的线程中去。 #include <pthread.h> #include <signal.h> int pthread_kill(pthread_t thread, int sig); 成功返回0,不成功返回非零的错误码并且无信号发送出去,必须检测的错误码: EINVAL sig是无效的或不被支持的信号码 ESRCH 没有现成对应于指定的ID 例:下面的代码段会使线程将它自己和整个进程都杀死,尽管pthread_kill将信号传递给了一个特定的线程,但处理信号的行为将影响到整个进程。(默认行为是终止进程的信号都是这个结果)。 if (pthread_kill(pthread_self(), SIGKILL) != 0) { fprintf (stderr, “Failed to commit suicide/n”); } 2.6.2为线程屏蔽信号 每个线程都有它自己的信号掩码。sigprocmask函数在创建其他线程之前,可以被逐线程调用。但当进程中有多个线程是就应该使用pthread_sigmask函数 #include <pthread.h> #include <signal.h> int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); 成功返回0,不成功返回非零的错误码, EINVAL 参数how 无效 how的取值: SIG_SETMASK 会使线程的信号掩码被set取代,阻塞set中所有的信号,但不阻塞其他 SIG_BLOCK 将set中的信号添加到现成现有的信号掩码中(阻塞包括set中的信号) SIG_UNBLOCK 从线程当前的信号掩码中将set中的信号删除(不再阻塞set中的信号) 2.6.3线程等待一个或多个信号发生 #include <signal.h> int sigwait(const sigset_t *restrict set, int *restrict signop); 成功返回0,不成功返回非零的错误码 set参数指定了线程等待的信号集,signop指向的整数将作为返回值,表明发送信号的数量。 如果信号集中的某个信号在sigwait调用的时候处于未决状态,那么sigwait将会无阻塞的返回,返回之前,sigwait将从进程中移出那些处于未决状态的信号。 如果多个线程在sigwait调用时,等待同一个信号,这是就会出现线程阻塞,当信号递送时,只有一个线程可以从sigwait中返回。 注意:为避免错误,线程在调用sigwait之前,必须阻塞那些他正在等待的信号。sigwait函数会自动取消信号集的阻塞状态,直到新的信号被递送。在返回之前,sigwait会恢复线程的信号屏蔽字。 2.6.3为信号处理指定专用的线程(多线程的进程中进行信号处理的推荐策略) 为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程作信号处理。这些专用线程可以进行函数调用,不需要担心那些函数是安全的,因为他们的调用来自正常的线程环境,而非传统的信号处理程序,传统的信号处理程序通常会中断线程的正常执行。 具体:主线程在创建线程之前阻塞所有的信号。信号掩码是从创建线程中继承的。这样,所有的线程都将信号阻塞了。然后,专门用来处理信号的线程对那个信号执行sigwait。 例: #include <pthread.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> int quitflag; sigset_t mask; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t wait = PTHREAD_COND_INITIALIZER; void *thr_fn(void *arg) { int err; int signo; for(;;) { err = sigwait(&mask, &signo); if (err != 0) { fprintf (stderr, "sigwait failed/n"); exit(1); } switch (signo) { case SIGINT: { printf("/ninterrupt/n"); break; } case SIGQUIT: { pthread_mutex_lock(&lock); quitflag = 1; pthread_mutex_unlock(&lock); pthread_cond_signal(&wait); return (0); } default: { printf("unexpeted signal %d/n", signo); exit(1); } } } } int main(void) { int err; sigset_t oldmask; pthread_t tid; sigemptyset (&mask); sigaddset (&mask, SIGINT); sigaddset (&mask, SIGQUIT); if ( (err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0 ) { fprintf (stderr, "SIG_BLOCK ERROR/n"); exit(1); } err = pthread_create(&tid, NULL, thr_fn, NULL); if (err != 0) { fprintf (stderr, "pthread_cteate error/n"); exit(2); } pthread_mutex_lock(&lock); while (quitflag == 0) { pthread_cond_wait(&wait, &lock); } pthread_mutex_unlock(&lock); quitflag = 0; if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) { fprintf (stderr, "SIG_SETMASK error/n"); exit(2); } exit(0); } 线程在开始阻塞SIGINT和SIGQUIT。新建线程继承了现有的信号屏蔽字。 因为sigwait会解除信号的阻塞状态,所以只有一个线程用于信号的接收。这使得对主线程进行编码是不必担心来自这些信号的中断。 2.7线程和fork(略) 线程和进程交互 2.8线程和IO 因为进程中的所哟欧线程共享相同的文件描述符。 考虑两个线程,在同一时间对同一个文件描述符进行读写操作 线程A 线程B lseek(fd, 300, SEEK_SET); lseek(fd, 700, SEEK_SET); read(fd, buf1, 100); read(fd, buf2, 100); 如果线程A执行lseek,然后线程B在线程A调用read之前调用lseek,那么两个线程最终会读取同一个记录。 为解决这个问题可以使用pread pwrite把偏移量的设定和数据的读写成为一个原子操作。 pread(fd, buf1, 100, 300); pread(fd, buf2, 100, 700); 2.9线程和信号安全的strerror和perror版本 用sigprocmask阻塞信号 用pthread_mutex_lock保持互斥访问 #include <errno.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <string.h> static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int strerror_r (int errnum, char *strerrbuf, size_t buflen) { char *buf; int error1; int error2; int error3; sigset_t maskblock; sigset_t maskold; if ( (sigfillset(&maskblock) == -1) || (sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1) ) { return errno; } if ( error1 = pthread_mutex_lock(&lock) ) { (void)sigprocmask(SIG_SETMASK, &maskold, NULL); return error1; } buf = strerror(errnum); if ( strlen(buf) >= buflen ) { error1 = ERANGE; } else { (void *)strcpy(strerrbuf, buf); } error2 = pthread_mutex_unlock(&lock); error3 = sigprocmask(SIG_SETMASK, &maskold, NULL); return error1?error1:(error2?error2:error3); } int perror_r(const char *s) { int error1; int error2; sigset_t maskblock; sigset_t maskold; if ( (sigfillset(&maskblock) == -1) || (sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1) ) { return errno; } if( error1 = pthread_mutex_lock(&lock) ) { (void)sigprocmask(SIG_SETMASK, &maskold, NULL); return error1; } perror(s); error1 = pthread_mutex_unlock(&lock); error2 = sigprocmask(SIG_SETMASK, &maskold, NULL); return error1?error1:error2; }