条件变量(通知法)
查询法,需要反复等着一个事件的发生:不停查看状态。上文的任务池就是一直查看状态。下面介绍通知法,使用的是线程的条件变量,pthread_cond_t:
相关函数:
pthread_cond_init();
pthread_cond_destroy();
---------------------- destroy and initialize condition variables
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);//动态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化,按照默认的初始化的属性
pthread_cond_broadcast();(发消息来打断一个wait)这里指的是pthread_cond_wait(下同)
pthread_cond_signal();
--------------------- broadcast or signal a condition
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);//把所有的等待都叫醒
int pthread_cond_signal(pthread_cond_t *cond);//叫醒任意一个
pthread_cond_wait();
pthread_cond_timedwait();
----------------------wait on a condition
#include <pthread.h>
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);//超时设置,若没有相应的行为时,就结束。
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);//一直等
wait相当于解锁等待,wait一定和一个或是多个位置的signal或broadcast是互相对应的。
筛质数更改
上游和下游不停的抢num,看状态。
上游往num中放一个任务之后,发一个通知。下游的几个都在wait,叫醒任意一个来计算就可以。如果下游都在忙,signal或是broadcast没有打断任何一个wait再看后续操作.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define LEFT 30000000
#define RIGHT 30000200
#define THRNUM 4
static void * thr_prime(void *p);
static int num=0;
static pthread_mutex_t mut_num=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_num=PTHREAD_COND_INITIALIZER;
int main()
{
int i,err;
pthread_t tid[THRNUM];
for(i=0;i<=THRNUM;i++)
{
err=pthread_create(tid+i,NULL,thr_prime,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create():%s\n",strerror(err));
exit(1);
}
}
for(i=LEFT;i<=RIGHT;i++)
{
pthread_mutex_lock(&mut_num);
while(num!=0)
{
pthread_cond_wait(&cond_num,&mut_num);
}
num=i;
pthread_cond_signal(&cond_num);
pthread_mutex_unlock(&mut_num);
}
pthread_mutex_lock(&mut_num);
while(num!=0)
{
pthread_mutex_unlock(&mut_num);
sched_yield();
pthread_mutex_lock(&mut_num);
}
num=-1;
pthread_mutex_unlock(&mut_num);
for(i=0;i<=THRNUM;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mut_num);
pthread_cond_destroy(&cond_num);
exit(0);
}
static void * thr_prime(void *p)
{
int i,j,mark;
while(1)
{
pthread_mutex_lock(&mut_num);
while(num==0)
{
pthread_cond_wait(&cond_num,&mut_num);
}
if(num==-1)
{
pthread_mutex_unlock(&mut_num);
break;
}
i=num;
num=0;
pthread_cond_broadcast(&cond_num);
pthread_mutex_unlock(&mut_num);
mark=1;
for(j=2;j<i/2;j++)
{
if(i%j==0)
{
mark=0;
break;
}
}
if(mark)
printf("[%d]%d is a primer\n",(int)p,i);
}
pthread_exit(NULL);
}
abcd的改编:使用通知法:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THRNUM 4
static pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
static int num=0;
static int next(int n)
{
if(n+1==THRNUM)
return 0;
return n+1;
}
static void *thr_func(void *p)
{
int n=(int)p;
int c='a'+n;
while(1)
{
pthread_mutex_lock(&mut);
while(num!=n)
{
pthread_cond_wait(&cond,&mut);
}
write(1,&c,1);
num=next(num);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
}
pthread_exit(NULL);
}
int main()
{
int i,err;
pthread_t tid[THRNUM];
for(i=0;i<THRNUM;i++)
{
err=pthread_create(tid+i,NULL,thr_func,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create():%s\n",strerror(err));
exit(1);
}
}
alarm(5);
for(i=0;i<THRNUM;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&mut);
pthread_cond_destroy(&cond);
exit(0);
}
运行描述:
创建四个线程,这四个同时在运行,先来看bcd这三个线程的,假设b抢到这个锁,然后发现num不是自己,就在那解锁等待,如果cd抢到锁,也是这样,如果最后a来了,抢到锁了,发现是自己,就开始写a,把num值写成1,就相当于b的那个线程,然后发通知,就叫醒bcd的wait,然后这三个线程开始抢锁,加入若是d抢到的,那么发现不是自己就继续解锁等待,等到最后b抢到了,然后就写b,唤醒其他的两个wait,这两个再继续抢锁
抢到发现是自己就打印,不是自己就解锁等待,这样反复执行下去。
信号量
信号量:(共享),多个资源,用的时候就减减,还回去的时候资源就加加
用互斥量和条件变量完成信号量的使用。哲学家就餐问题。
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用(百度描述)。
筛质数,以信号量的方法实现。
main.c文件内容:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "mysem.h"
#define LEFT 30000000
#define RIGHT 30000200
#define THRNUM (RIGHT-LEFT+1)
#define N 4
static void * thr_prime(void *p);
static mysem_t *sem;
int main()
{
int i,err;
pthread_t tid[THRNUM];
sem=mysem_init(N);
if(sem==NULL)
{
fprintf(stderr,"mysem_init() failed\n");
exit(1);
}
for(i=LEFT;i<=RIGHT;i++)
{
mysem_sub(sem,1);
err=pthread_create(tid+(i-LEFT),NULL,thr_prime,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create():%s\n",strerror(err));
exit(1);
}
}
for(i=LEFT;i<=RIGHT;i++)
pthread_join(tid[i-LEFT],NULL);
mysem_destroy(sem);
exit(0);
}
static void * thr_prime(void *p)
{
int i,j,mark;
i=(int )p;
mark=1;
for(j=2;j<i/2;j++)
{
if(i%j==0)
{
mark=0;
break;
}
}
if(mark)
printf("%d is a primer\n",i);
sleep(5);
mysem_add(sem,1);
pthread_exit(NULL);
}
mysem.c文件内容
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "mysem.h"
struct mysem_st
{
int value;
pthread_mutex_t mut;
pthread_cond_t cond;
};
mysem_t *mysem_init(int initval)
{
struct mysem_st *me;
me=malloc(sizeof(*me));
if(me==NULL)
{
return NULL;
}
me->value=initval;
pthread_mutex_init(&me->mut,NULL);
pthread_cond_init(&me->cond,NULL);
return me;
}
int mysem_add(mysem_t *ptr,int n)
{
struct mysem_st *me=ptr;
pthread_mutex_lock(&me->mut);
me->value+=n;
pthread_cond_broadcast(&me->cond);
pthread_mutex_unlock(&me->mut);
return n;
}
int mysem_sub(mysem_t *ptr,int n)
{
struct mysem_st *me=ptr;
pthread_mutex_lock(&me->mut);
while(me->value<n)
{
pthread_cond_wait(&me->cond,&me->mut);
}
me->value-=n;
pthread_mutex_unlock(&me->mut);
return n;
}
int mysem_destroy(mysem_t *ptr)
{
struct mysem_st *me=ptr;
pthread_mutex_destroy(&me->mut);
pthread_cond_destroy(&me->cond);
free(me);
return 0;
}
mysem.h文件内容:
#ifndef MYSEM_H__
#define MYSEM_H__
typedef void mysem_t;
mysem_t *mysem_init(int initval);
int mysem_add(mysem_t *,int);
int mysem_sub(mysem_t *,int);
int mysem_destroy(mysem_t *);
#endif
makefile文件内容:
CFLAGS+=-pthread
LDFLAGS+=-pthread
all:mysem
mytbf:main.o mysem.o
gcc $^ -o $@ $(CFLAGS) $(LDFLAGS)
clean:
rm -rf *.o mysem
make编译以后,如果出现问题,那就选择一个一个来编译:
cc -pthread -c -o main.o main.c
gcc main.o mysem.o -o mysem -pthread -pthread
读写锁
读写锁:相当于互斥量和信号量的综合使用,包含两种锁,一种是读锁,一种是写锁。
读锁:相当于共享的内容,有点像信号量
写锁:像是互斥量,是一种互斥的内容
如果有一个文件,要么是读锁,要么是写锁,如果当前文件加的是读锁,如果再来一个读者,再加上一个读锁是可以实现的。如果当前文件本身是一个写锁,那么再加一个读锁或是写锁,是不可以实现的。因为如果在写的时候,别人在读会出现偏差,也不会是两个人同时在写。所以写锁相当于互斥锁,读锁相当于共享锁。整个过程的模拟是这样的:有一个读者来读文件,文件就加上读锁,又有一个读者到来,看到文件加的是读锁,那么它也可以加上读锁,进行读的操作。如果这两个读者在操作的同时,来了一个写者,发现这个文件有人在读,那么就等待,写锁就加不上。(但是如果源源不断有读者来读,写者没有机会加上写锁,那么写者会出现写者饿死现象)。那就需要以下的操作:当有一个写者到来了,企图给这个文件加上写锁时候,这个写锁是加不上的,文件真正加的是读锁,但只要有这个写者来了,写者在等待的时候,那么这个文件在外看来,就如同加了写锁一般,但是实际上加的是读锁,那么后来的那些读者,在看到这个文件的时候,就不会看到文件的真正的状态,不会看到这个读的状态,而以为文件打了写锁。所以后面的读者,暂时先等待,等到前面两个读者读完走了,写者真的把文件加上了写锁,后面的读者才能够把文件加上读锁,来继续读。这样就可以避免写者饿死的现象。