信号量:
具有等待队列的计数器,
做什么:实现进程/线程间同步与互斥
计数器用于判断现在是否有资源信号量有更多的计数–>标记有多少资源是否可以操作–>实现同步:–>唤醒和等待
信号量:
功能计数大于0代表有资源可以操作(-1)往下走返回(内部对计数减1操作),代表资源被使用之后,操作完毕之后,唤醒,回收,然后释放资源(计数器+1)通知队列上的pcb资源空出一个,将互斥锁的计数和条件变量里的等待与唤醒合在一起
计数小于等于0没有资源等待–>等待在队列上面
如何操作:–>是POSIX标准信号量(比systemV好用)
Posix是库函数而systemV是系统调用
计数器是一个资源计数
获取资源:
计数大于0表示有资源可以操作计数-1;
不大于0表示没有资源可以操作陷入休眠
释放资源:
计数加1,并唤醒等待在队列上的pcb
Sem_intit sem_destroy sem_wait sem_timedwait(限时等待) sem_post
初始化 销毁 信号量资源计数 限时等待 唤醒等待的pcb
判断判断计数大于0
则返回计数如果小于0
陷入休眠
Shift+tabale终端互换alt+table切换窗口
//posix标准信号量基本使用哪个
信号量实现同步:
2 //sem_init
3 //sem_destroy
4 //sem_wait
5 //sem_timedwait
6 //int sem_wait(sem_t sem);
7 //阻塞
8 //int sem_trywait(sem_t sem);
9 //非阻塞
10 //int sem_timedwait(sem_t sem, const struct timespec abs_timeout);
11 //限时阻塞
12 //内部判断计数是否大于,大于0立即返回计数减1不大于0:sem_wait陷入等待sem_trywait 错误
13 //返回sem_timedwait等待一段时间之后错误返回
14 //sem_post
15 //int sem_init(sem_t sem, int pshared, unsigned int value);
16 //参数1信号量变量,参数2:进程间通信操作系统创建了一个共享内存线程间通
17 //信同步与互斥虚拟地址空间就可以实现信号量为0信号量则共享于一个进程当中的线程> 之间–>0为线程间的同步与互斥不为0应用于进程间同步与互斥
18 //第三个参数:信号量是个计数器初始化到底有多少资源计数–>信号量的初值
#include <semaphore.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include<unistd.h>
23 #include<pthread.h>
24 sem_t sem;
25 void the_pro(voidarg){
26 //1s生产一包生产出来立即就吃了—>实现了信号量实现同步
27 while(1){
28 sleep(1);
29 printf("create something\n ");
30 //生产者计数加+1并且通知等待的人
31 //int sem_post(sem_tsem);信号量计数+1并且唤醒等待中的pcb
32 sem_post(&sem);
33 }
34 return NULL;
35 }
W> 36 void the_buy(voidarg){
37 while(1){
38 //条件变量判断外部条件是否满足然后陷入休眠只提供了休眠与唤醒的功能信号量本身内部就有一个计数已经做到了资源计数的判断(内部进行),信号量不需要外部
40 //条件本身就是条件
41 sem_wait(&sem);
42 printf("buy something\n ");
43 }
44 return NULL;
45 }
46
47 int main(){
48 pthread_t tid1,tid2;
49 pthread_create(&tid1,NULL,the_pro,NULL);
50 int ret;
51 //初始化实现同步
52 //int sem_init(sem_t sem, int pshared, unsigned int value);
53 //参数1信号量变量,参数2:进程间通信操作系统创建了一个共享内存线程间通
54 //信同步与互斥虚拟地址空间就可以实现信号量为0信号量则共享于一个进程当中的线程之间–>0为线程间的同步与互斥不为0应用于进程间同步与互斥
55 //第三个参数:信号量是个计数器初始化到底有多少资源计数–>信号量的初值
56 //sem信号量,pshared应用范围(0->线程间同步互斥,非0进程间同步与互斥)
57 //value:信号量的初值–>和资源一一对应
//先生产在买–>初值为0
59 sem_init(&sem,0,0);
W> 60 if(ret!=0){
61 printf(“pthread create error\n”);
62 return -1;
63 }
64 pthread_create(&tid2,NULL,the_buy,NULL);
65 if(ret!=0){
66 printf(“pthread create errno\n”);
67 }
68 //线程等待
69 pthread_join(tid1,NULL);
70 pthread_join(tid2,NULL);
71 //信号量的销毁
72 //int sem_destroy(sem_t sem);
73 sem_destroy(&sem);
74 return 0;
75 }
运行结果如下:
信号量实现互斥:
互斥锁实现互斥0和1信号量也是保证0 1
sem_t sem;
W> 7 void the_pro(voidarg){
8 while(1){
9 sem_wait(&sem);//别人就能停车了,判断一下计数是否大于0不大于0等待大于0停车
10 sleep(1);
11 printf("create something\n ");
12 sem_post(&sem);//停车出来后计数+1
13 }
14 return NULL;
15 }
W> 16 void* the_buy(voidarg){
17 while(1){
18 sem_wait(&sem);//停车的时候计数减1
19 printf("buy something\n ");
20 sem_post(&sem);//车开走了计数+1
21 }
22 return NULL;
23 }
int main(){
26 pthread_t tid1,tid2;
27 pthread_create(&tid1,NULL,the_pro,NULL);
28 int ret;
29 sem_init(&sem,0,0);
W> 30 if(ret!=0){
31 printf(“pthread create error\n”);
32 return -1;
33 }
34 pthread_create(&tid2,NULL,the_buy,NULL);
35 if(ret!=0){
36 printf(“pthread create errno\n”);
37 }
38 pthread_join(tid1,NULL);
39 pthread_join(tid2,NULL);
40 sem_destroy(&sem);
41 return 0;
42 }
结果为:
不动了发现:初值为0而且都wait–>停车场满了进不去所以计数至少为1互斥
sem_t sem;
W> 7 void the_pro(voidarg){
8 while(1){
9 sem_wait(&sem);//别人就能停车了,判断一下计数是否大于0不大于0等待大于0停车
10 sleep(1);
11 printf("等待停车\n ");
12 sem_post(&sem);//停车出来后计数+1
13 }
14 return NULL;
15 }
W> 16 void the_buy(voidarg){
17 while(1){
18 sleep(1);
19 sem_wait(&sem);//停车的时候计数减1
20 printf("停车\n ");
21 sem_post(&sem);//车开走了计数+1
22 }
23 return NULL;
24 }
int main(){
27 pthread_t tid1,tid2;
28 pthread_create(&tid1,NULL,the_pro,NULL);
29 int ret;
30 sem_init(&sem,0,1);//只能为1才能实现互斥,如果为2则都能进去不能实现互斥(用于互斥,只能为1,同步为0)
W> 31 if(ret!=0){
32 printf(“pthread create error\n”);
33 return -1;
34 }
35 pthread_create(&tid2,NULL,the_buy,NULL);
36 if(ret!=0){
37 printf(“pthread create errno\n”);
38 }
39 pthread_join(tid1,NULL);
40 pthread_join(tid2,NULL);
41 sem_destroy(&sem);
42 return 0;
43 }
使用c++中的vector实现线程安全的环形队列
属于容器,Vector是线性表就是数组
两个指针从数组首地址开始一个为r一个为w,向里面写的时候w指向下一个位置,如果相等代表现在队列为空,当w走到数组末尾要返回起始位置如果下一个位置是r指针的位置,代表队列已近写满了;
Std::vector _list;—>要初始化的,初始化有多少个结点
Int_cap;//大小
Mutex–>保证互斥多对多的时候保证安全访问操作-
信号量:多个人放多个人取使用两个信号量–>两个队列–>分别唤醒
Sem_t sapce;//有多少个空间(数组大小,结点大小)可以写入数据,如果这个队列是4个结点这个sapce初值就是4
Sem_t used;//已近使用了空间;一开始没有可以使用的,因为环形队列值为空代表没有任何一个节点里面有数据,代表used初值为0
Sapce和used实现资源计数和同步
环形队列里面添加数据的时候用std:queue添加数据的时候直接push到尾结点了(队尾),但是向数组里面放数据的时候space和used只是资源计数但是并没有告诉我们w指针现在到哪里了意味着有多个写线程他们写数据的时候完全有可能写到同一个地方所有还需要一个int pro_step来描述生产者写入的位置int_con_step来表示消费者获取数据在哪个位置,也就是w和r的位置
所以这个类里面的成员有:Std::vector _list;Int_cap;Mutex;Sem_t sapce;int pro_step;Sem_t used;int_con_step,就可以实现这个队列
也要有入队出队判断队列是否为空/为满
Queuepush queuepop queuseisempty queueisfull
//基于c++中的vector容器实现线程安全的环形队列
2 //vector是一个线性表(数组)
3 #include
4 #include
5 #include<pthread.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8 #include<semaphore.h>
9 #include<stdio.h>
10 #include
11 //私有成员以前命名为my_list—>演变成 _list,主要是区分类成员和形参成员
12 class RingQueue{
13 private:
14 std::vector _list;
15 int _cap;
16 pthread_mutex_t _mutex;
17 sem_t _space;
18 sem_t _data;//多少数据可以获取
19 int _pro_step;
20 int _cons_step;
public:
22 //公有的第一个为构造函数
23 RingQueue(int cap=10): _list(cap), _cap(cap),_pro_step(0), _cons_step(0){
25 sem_init(&_space,0,cap);//初始化为线程间同步与互斥空闲空间大小是数组大小
26 //所以初值为cap
27 sem_init(&_data,0,0);//没有数据写入为0
28 pthread_mutex_init(&_mutex,NULL);
29 }
30 ~RingQueue(){
31 //析构函数
32 sem_destroy(&_space);
33 sem_destroy(&_data);
34 }
35 bool QueuePush(int data){//传值想数组添加一个数就好不用传地址
36 //记住queuepush不是原子操作
37 sem_wait(&_space);//生产者有空闲空间才能向下走没有空闲空间不能放数据,判
38 //断队列是否满了,sapce能走保证肯定有空闲空间
39 pthread_mutex_lock(&_mutex);//上锁
40 _list[_pro_step]=data;//生产者
_pro_step++;
42 //法1 if(_pro_step==_cap){//放满了
43 // _pro_step=0;
44 //}
45 //法2
46 _pro_step%=_cap;//等于cap取余为0
47 pthread_mutex_unlock(&_mutex);
48 sem_post(&_data);//唤醒data放了一个数据代表现在已经有一个空间可用,有资源
49 //可以操作给data里面加1
50 return true;
51 }
52 //如果cpu切换调度的时候先运行的queuepop,那么一开始一定没有数据会陷入休眠,不是
53 //条件变量并不会解锁代表push获取不到锁就没办法放数据了,就没办法读数据了卡死了
54 //pthread_mutex_lock保护的是wait的资源,信号量的计数不需要保护,所以应该将
55 //lock放在wait下,如果sem_wait进来代表有资源然后进行加锁获取数据然后解锁space+1
56 //所以代表其他的消费者也能获取,如果自己获取不到代表其他消费者依然会走到wait
57 //使用条件变量把加锁放在外面,因为条件判断本身就是临界资源所以要保护它,但是对于
58 //信号量来说本身内部计数不需要保护为原子操作所以就不需要把加锁放在sem_wait外部
59 //不需要保护它,只需要保护数组的操作就可以了
60 bool QueuePop(int data){//传地址是为了获取这个数据,用int,如果获取的是地址用int**;
62 sem_wait(&_data);//判断data有数据代表有数据可以读
63 pthread_mutex_lock(&_mutex);
64 data=_list[_cons_step];
65 _cons_step++;
66 _cons_step%=_cap;
67 pthread_mutex_unlock(&_mutex);
68 sem_post(&_space);//data取了一个数据就代表space空出来一个空间
69 return true;
70 }
71 };
72 void thr_con(voidarg){
73 RingQueueq=(RingQueue)arg;
74 while(1){
75 //获取数据
76 int data;
77 q->QueuePop(&data);
78 printf(“consumer get data %d\n”,data);
79 }
80 return NULL;
}
82 void thr_pro(voidarg){
83 RingQueueq=(RingQueue)arg;
84 int* i=0;//i
85 while(1){
86 sleep(1);
87 printf(“the producter has space %d\n”,i);
88 q->QueuePop(i++);
89 }
90 return NULL;
91 }
92 //所以queueisempt和queueisfull没必要存在,因为space和used本来就是计数计数为0代表没有资源,
94 int main(){
95 pthread_t ctid[4],ptid[4];//4个生产者4个消费者
96 int ret,i=0;
97 RingQueue q;
98 for(;i<4;i++){
99 ret=pthread_create(&ctid[i],NULL,thr_con,(void)&q);
100 if(ret!=0){
printf(“create pthread errno\n”);
102 return -1;
103 }
104 }
105 for(;i<4;i++){
106 ret=pthread_create(&ptid[i],NULL,thr_pro,(void*)&q);
107 if(ret!=0){
108 printf(“create pthread errno\n”);
109 return -1;
110 }
111 }
112 for(;i<4;i++){
113 pthread_join(ctid[i],NULL);
114 pthread_join(ptid[i],NULL);
115 }
116 return 0;
117 }
读写锁:–>写互斥读共享–>通过两个计数器控制
两个人同时进行写操作是不行的–>写互斥–>读共享
写的时候不能读,读的时候可以一起读,读的时候不能写
如何实现:互斥锁拿计数实现,读写锁内部也是互斥,两个计数read和write计数,写的时候write+1表示有人正在写,写的时候判断write是否为0,不为0代表有人正在写,代表自己不能写并且除了判断write之外判断read计数是否为0如果read也不为0,write为0还是写不了
写的时侯要判断write和read都为0才能写读的时候只需要判断write是否为0,为0就可以读了
Write:写的时候判断写计数和读计数若都为0则可以写操作,否则阻塞(以前的阻塞是死等)–>自旋
Read:只需要判断写计数是否为0,若为0则可以读否则阻塞
读写锁是使用自旋锁实现的(不满足条件循环判断而不是陷入休眠–>一直循环判断回造成CPU耗费资源高)–>为什么用自旋锁判断:知道等待的时机不会很长–>操作时间尽量不要太长,
读写锁使用场景—>操作时间段,并且读操作多而写操作少–>读写锁默认是读优先的–>如果让写优先则设置写优先
加写锁的时候,判断读计数和写计数都为0则能够加锁,否则自选等待
加读锁的时候,判断写计数,若为0,则能够加锁,否则自选等待
读写锁的实现:不满足条件循环判断
读写锁是以自选实现,意味着等待会非常耗费CPU资源,因此使用于写者少读者多的场景
读写锁应该写优先因为读者多会导致写饥饿,但是读写锁默认读优先
使用读写锁若要写优先需要设置读写锁属性
接口:
pthread_rwlock_init(pthread_rwlock_t,属性)
Pthread_rwlock_destroy()
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//加读锁阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//加读锁非阻塞报错直接返回
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//加写锁阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//加写锁非阻塞直接报错返回
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁—>内部有标记封装
int pthread_rwlockattr_destroy(pthread_rwlockattr_t attr);
int pthread_rwlockattr_init(pthread_rwlockattr_t attr);//读写锁属性变量的初始化传入的属性()
pthread_rwlockattr_setkind_np(pthread_rwlockattr_t attr,int pref);
Man手册没有直接 vim /usr/include/pthread.h 里面找
extern int pthread_rwlockattr_setpshared (pthread_rwlockattr_t __attr,
952 int __pshared)
953 __THROW __nonnull ((1));
设置读写属性优先级的接口(np轻合性)
Pref有三种选择
PTHREAD_RWLOCK_PREFER_READER_NP(默认设置)读优先,可能导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITTE_NP写者优先,目前有bug导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP一致
PTHREAD_RWLOCK_PREFER_WRITRE_NONRECUPSIVE_NP写者优先但是写者不能递归加锁
enum {
127 PTHREAD_RWLOCK_PREFER_READER_NP,
128 PTHREAD_RWLOCK_PREFER_WRITER_NP,
129 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
130 PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
131 };
//读写锁的基本使用:设置写优先
2 #include <stdio.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<pthread.h>
6 pthread_rwlock_t rwlock;
W> 7 void thr_reader(voidarg){
8 while(1){
9 pthread_rwlock_rdlock(&rwlock);//加读锁
10 printf(“thread reader–\n”);
11 usleep(1000);
12 pthread_rwlock_unlock(&rwlock);//解锁
13 }
14 return NULL;
15 }
W> 16 voidthr_writer(voidarg){
17 while(1){
18 pthread_rwlock_wrlock(&rwlock);//加写锁
19 printf(“thread writer—\n”);
20 usleep(1000);
pthread_rwlock_unlock(&rwlock);//写锁
22 }
23 return NULL;
24 }
25 int main(){
26 pthread_t rtid[4],wtid[4];
27 int i,ret;
28 //设置属性初始化改变优先级
29 //int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
30 //int pthread_rwlockattr_init(pthread_rwlockattr_t attr);
31 //int phtread_rwlockattr_setkind_np(pthread_rwlockattr_tattr,
32 //int _pref)
33 //attr读写锁属性,pref优先级属性
34 //PTHREAD_RWLOCK_PREFER_READER_NP//读优先
35 // 128 PTHREAD_RWLOCK_PREFER_WRITER_NP,//读优先
36 // 129 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,//写优先
37 // 130 PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
38 // 读写锁 默认读优先
39 // 比如写优先加写锁但是一直有人在读等到正在读的人读完但是后序有人继续要加读
40 // 锁它是加不上的
pthread_rwlockattr_t attr;
42 pthread_rwlockattr_init(&attr);
43 pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_READER_NP);
44 //读写锁初始化
45 pthread_rwlock_init(&rwlock,&attr);
46 //销毁属性
47 pthread_rwlockattr_destroy(&attr);
48 for(i=0;i<4;i++){
49 ret=pthread_create(&rtid[i],NULL,thr_reader,NULL);
50 if(ret!=0){
51 printf(“create thread erron\n”);
52 return -1;
53 }
54 }
55 for(i=0;i<4;i++){
56 ret=pthread_create(&wtid[i],NULL,thr_writer,NULL);
57 if(ret!=0){
58 printf(“create thread erron\n”);
59 return -1;
60 }
}
62 for(i=0;i<4;i++){
63 pthread_join(rtid[i],NULL);
64 pthread_join(wtid[i],NULL);
65 }
66 pthread_rwlock_destroy(&rwlock);//读写锁的销毁
67 return 0;
68 }
信号量和读写锁
最新推荐文章于 2022-08-11 11:33:53 发布