线程同步


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

你可能感兴趣的:(线程同步)