现在的位置: 首页 > 综合 > 正文

线程同步

2013年09月05日 ⁄ 综合 ⁄ 共 18005字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.