线程同步

    技术2025-07-10  12

      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; }

    最新回复(0)