1. 上锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_trylock(pthread_mutex_t *nptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
互斥锁是协作性锁。这就是说,如果共享数据是一个链表,那么操纵该链表的所有线程都必须在实际操纵前获取该锁。不过也没办法防止某个线程不首先获取该互斥锁就操纵该链表。
struct{
pthread_mutex_t mutex;
int buff[MAX];
int nput;
int nval;
}shared={PTHREAD_MUTEX_INITIALIZER};
生产者-消费者问题
多个生产者 vs 单个消费者,我们只考虑生产者之间的同步问题,不考生产者和消费者之间的同步问题,因为我们确保所有的生产者创建完毕才创建消费者。
生产者代码:
void *produce(void *arg)
{
for(;;){
pthread_mutex_lock(&shared.mutex);
if(shared.nput>=nitems){
pthread_mutex_unlock(&shared.mutex);
return NULL;
}
shared.buff[shared.nput]=shared.nval;
shared.nput++;
shared.nval++;
pthread_mutex_unlock(&shared.mutex);
*((int*)arg)+=1;
}
}
对比上锁与等待
生产者和消费者,这里消费者在生产者创建完成之后立马创建,所以同步生产者和消费者,消费者不能去取那些未存放的缓冲区,在没有条件变量的情况下,消费者需要for(;;)轮询是否有生产者生产产品,这里我们需要另外一种类型的同步,它允许一个线程(进程)睡眠到某个事件发生--这就是条件变量的功能。
2.条件变量
互斥锁用于上锁,条件变量用于等待。这俩种不同类型的同步都是需要的。条件变量的类型是pthread_cond_t的变量。
int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
这俩个函数等待或由之得以通知的“条件”,其定义由我们选择:我们在代码中测试这种条件。
每个条件变量都有一个互斥锁与之关联。我们调用pthread_cond_wait等待某个条件为真时,还会指定其条件变量的地址和所关联的互斥锁地址。
给条件变量发送信号代码:
struct{
pthread_mutex_t mutex;
pthread_cond_t cond;
维护本条件的各个变量
}var={PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,...};
pthread_mutex_lock(&var.mutex);
设置条件为真
pthread_cond_signal(&var.cond);
测试条件并进入睡眠以等待该条件变为真:
pthread_mutex_unlock(&var.mutex);
while(条件为假)
pthread_cond_wait(&var.cond,&var.mutex);
修改条件
pthread_mutex_unlock(&var.mutex);
-----------------------------------------------
调用pthread_cond_wait(&nready.cond,&nread.mutex)的进程会进入睡眠,该函数执行下面俩个动作
(1)给互斥锁nread.mutex解锁
(2)把调用线程投入睡眠,直到另外某个线程就本条件变量调用pthread_cond_signal。
pthread_cond_wait在返回前重新给互斥锁nready.mutex上锁。
3.读写锁
以后再介绍吧
4.信号量
信号量是一种用于提供不同进程或一个给定进程的不同线程间同步手段的原语。
Posix提供俩类信号量:有名信号量和基于内存的信号量,后者也称为无名信号量。
我们主要研究无名信号量
(1)int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
测试指定信号量的值,如果该值大于0,那就将他减1并立即返回。如果该值等于0,调用线程就被投入睡眠,直到该值变为大于0,这时再将它减1,函数随后返回。
sem_trywait在信号量等于0时并不投入睡眠,而是返回一个EAGIN错误。
如果被某个信号中断,sem_wait也可能过早返回,所返回的错误是EINTR。
int sem_getvalue(sem_t *sem,int *valp);
当一个线程使用完某个信号量,它应该调用sem_post。如果该信号量当前上锁,那么返回值是0,或为某个负数,其绝对值等待的线程数目。
互斥锁、条件变量、信号量区别:
首先:互斥锁必须总是由给它上锁的线程解锁;信号量没有这种限制:一个线程可以等待某个信号量,而另一个线程可以挂出该信号量。
其次,既然每个信号量有一个与之关联的值,它由挂出操作加1,由等待操作减1,那么任何线程都可以挂出一个信号(譬如说将他的值由0变为1),即使当时没有线程在等待该信号量值变为正数也没有关系。然而,如果某个线程调用了pthread_cond_signal,不过当时没有任何线程阻塞在pthread_cond_wait调用中,那么发往相应条件变量的信号将丢失。
最后,在各种各样的同步技巧(互斥锁、条件变量、读写锁、信号量)中,能够从信号处理程序中安全调用的唯一函数是sem_post。
互斥锁是为上锁优化的,条件变量是为等待优化的,信号量既可以用于上锁,也可以用于等待,所以可能导致更多的开销。
创建无名信号量:
int sem_init(sem_t *sem,int shared,unsigned int value);
int sem_destroy(sem_t *sem);
sem:指向应用程序必须分配的sem_t变量。
shared:0,那么初始化的信号量是在同一进程的各个线程间共享,否则该信号量是在进程间共享的(包括父子进程,所以父子进程间这个参数也是1),当shared非零时,该信号量必须放在某种类型的共享内存区中,而即使将使用它的所有进程都要能访问该共享内存区。
value:信号量的初值
当不需要使用与有名信号量关联的名字时,可改用基于内存的信号量。彼此无亲缘关系的不同进程需要使用信号量时,通常使用有名信号量。其名字就是各个进程标识信号量的手段。
我们在图1-3中说过,基于内存的信号量至少具有随进程的持续性,然而他们真正的持续性却取决于存放信号量的内存区类型。只要含有某个基于内存信号量的内存区保持有效,该信号量就一直存在。
- 如果某个基于内存的信号量时由单个进程内的各个线程共享(sem_init的shared参数是0),那么该信号量具有随进程的持续性,当该进程终止时它消失。
- 如果某个基于内存的信号量时在不同进程间共享的(sem_init的shared为1),那么该信号量必须放在共享内存中,因而只要该共享区仍然存在,该信号量也就继续存在。共享内存区具有随内核的持续性,这意味着服务器可以创建一个共享内存区,在该共享内存区中初始化一个Posix基于内存的信号量,然后终止。一段时间后,一个或多个客户可以打开该共享内存区,访问存放在其中的基于内存的信号量。