假设有一个数组。
线程1往数组中放数据,线程2读取数据,然后删掉。
一阶段
那么写法应该这样:
读取线程1:
while(true)
lock(a)
if(array.length > 0 )
read();
unlock(a)
写线程2:
lock(a)
write();
unlock(a)
对于线程1而言,每次需要检查数组是否为空,否则,来判断是否要读。
忙等的方式显然不是特别好的方式。
关于这一点参考文章:
https://blog.csdn.net/flymachine/article/details/10012959
二阶段
想象下存在这么一个,condition的能力。
读取线程1:
while(true)
wait(cond);
lock(a)
read();
unlock(a)
写线程2:
lock(a)
write();
unlock(a)
signal(cond)
上面的方式,可以让线程1,等待触发条件,直到条件发生,才运行,而运行前都在阻塞不占用CPU。
三阶段
上面的方式对于只有一个读或只有一个写是没有问题的。
假设不止一个读进程,也不止一个写进程呢?会发生多线程操作个cond条件。所以不得不加个锁。
先明白错
该怎么写?下面这个方式明显是不行的,因为如果读取线程获取到锁以后,就等待了,如果线程2运行的比线程1晚的话,那么就永远无法抢到锁,也就无法发信号量,从而死锁。
读取线程1:
while(true)
lock(mutex);
wait(cond);
unlock(mutex);
lock(a)
read();
unlock(a)
写线程2:
lock(a)
write();
unlock(a);
lock(mutex);
signal(cond)
unlock(mutex);
如何解决?
下面是一种想出来的解决思路:配合非阻塞的锁,以及条件等待,来、无锁的条件等待。
然后知道对
读取线程1:
while(true)
while(size=0){// 复查条件,因为释放了锁以后,可能多个线程1被唤醒。
lock(mutex);
wait(cond,mutex){
while(!trylock(进入)){//这里确保了每次只有一个读会进入。
unlock(mutex);//既然已经有别的读线程获取了“进入”锁,那就放弃了,这样也不会阻塞其它的进程。包括其它的读,和其它的写。
wait(A);//等待进入锁释放的信号,这样不会有忙等的低效率
lock(mutex);
}
unlock(mutex);
开始等待();
unlock(进入);
signal(A);//进入锁释放了
//这个前后结果一样,但是效率上一点点小差别,因为先释放信号量,其它线程唤醒后,尝试获取锁,然后会失败,不过下次再实验的时候就会成功的。后释放,获取的时候至少有一个线程会成功。
// signal(A);
// unlock(我已经进来了);
}
}
lock(a)
read();
unlock(a)
写线程2:
lock(a)
write();
unlock(a);
lock(mutex);
signal(cond)
unlock(mutex);
标准的用法。其实是因为pthread_cond_wait里面做了unlock 操作和然后又做了lock操作,内部的具体实现不清楚,我能想到的是上面的实现方式。
为什么最后还要外面unlock一次,我理解是两点。
1.为了让函数对称,为了每个lock 都有一个unlock 所以pthread_cond_wait 内部要使用unlock一定也要有对应的lock.
2.lock 后,可能会产生相应的对cond 对修改。这样lock 也是必要的。不过函数内部lock之后,不能unlock ,因为外面会有一个unlock 和他呼应。
In Thread1:
pthread_mutex_lock(&m_mutex);
pthread_cond_wait(&m_cond,&m_mutex);
读
pthread_mutex_unlock(&m_mutex);
In Thread2:
pthread_mutex_lock(&m_mutex);
写
pthread_cond_signal(&m_cond);
pthread_mutex_unlock(&m_mutex);
百科上有个例子不错:
https://baike.baidu.com/item/pthread_cond_wait/3011997?fr=aladdin
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
struct node {
int n_number;
struct node *n_next;
} *head=NULL; /*[thread_func]*/
/*释放节点内存*/
static void cleanup_handler(void*arg) {
printf("Clean up handler of second thread.\n");
free(arg);
(void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg) {
struct node*p=NULL;
pthread_cleanup_push(cleanup_handler,p);
pthread_mutex_lock(&mtx);
//这个mutex_lock主要是用来保护wait等待临界时期的情况,
//当在wait为放入队列时,这时,已经存在Head条件等待激活
//的条件,此时可能会漏掉这种处理
//这个while要特别说明一下,单个pthread_cond_wait功能很完善,
//为何这里要有一个while(head==NULL)呢?因为pthread_cond_wait
//里的线程可能会被意外唤醒,如果这个时候head==NULL,
//则不是我们想要的情况。这个时候,
//应该让线程继续进入pthread_cond_wait
while(1) {
while(head==NULL) {
pthread_cond_wait(&cond,&mtx);
}
//pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
//然后阻塞在等待队列里休眠,直到再次被唤醒
//(大多数情况下是等待的条件成立而被唤醒,唤醒后,
//该进程会先锁定先pthread_mutex_lock(&mtx);,
//再读取资源用这个流程是比较清楚的
/*block-->unlock-->wait()return-->lock*/
p=head;
head=head->n_next;
printf("Got%dfromfrontofqueue\n",p->n_number);
free(p);
}
pthread_mutex_unlock(&mtx);//临界区数据操作完毕,释放互斥锁
pthread_cleanup_pop(0);
return 0;
}
int main(void) {
pthread_t tid;
int i;
struct node *p;
pthread_create(&tid,NULL,thread_func,NULL);
//子线程会一直等待资源,类似生产者和消费者,
//但是这里的消费者可以是多个消费者,
//而不仅仅支持普通的单个消费者,这个模型虽然简单,
//但是很强大
for(i=0;i<10;i++) {
p=(struct node*)malloc(sizeof(struct node));
p->n_number=i;
pthread_mutex_lock(&mtx);//需要操作head这个临界资源,先加锁,
p->n_next=head;
head=p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);//解锁
sleep(1);
}
printf("thread1wannaendthecancelthread2.\n");
pthread_cancel(tid);
//关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,
//子线程会在最近的取消点,退出线程,而在我们的代码里,最近的
//取消点肯定就是pthread_cond_wait()了。
pthread_join(tid,NULL);
printf("Alldone--exiting\n");
return 0;
}
别人的一些思路:个人觉得讲的一半清楚。
http://blog.chinaunix.net/uid-27164517-id-3282242.html