信号量在本质上是PCB等待队列+计数器
计数器:对资源的计数,会影响信号量的等待接口和发送接口(唤醒接口)的逻辑
如果有一个停车场,内有8个车位,此时停满了车,又有2辆车想停进去,即处于等待队列,此时资源计数器的值便为-2
当一个线程调用发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果还是小于0,此时需要通知等待队列吗?
需要,即开走了一辆车,在等待队列中的一辆车停了进去,资源计数器进行加1操作,变成-1,但是等待队列有依然存在等待的车辆,所以需要通知
当一个线程调用发送接口之后,资源计数器进行加1操作,此时,加1操作之后的资源计数器的结果是大于0,此时需要通知等待队列吗?
不需要,假设这个停车场只停了一辆车,随后这辆车开走了,资源计数器中的数值为8,但是等待队列中并没有车辆在等待,所以不需要通知
条件变量:
程序员需要自己把握资源的数量
信号量:
信号量会自己维护资源的数量,只需要在初始化信号量的时候指定资源的数量
信号量可以完成互斥,也可以完成同步
互斥:资源计数器的初始值设置1,线程A拿到信号量,线程B一定拿不到
同步:
设定一个写线程和一个读线程,都从起始位置开始,设定一个数组,读线程初始化为0,写线程初始化为数组空间大小,
写线程在写入后到下一个位置,写线程初始值-1,随后唤醒读线程,读线程计数器+1,在读完后进入下一个位置,读线
程计数器-1并通知写线程进行写,写线程计数器+1
写线程找下一个写的位置:
此时位置 = n
下一个位置 = (n + 1)% 数组大小
sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem:信号量
pshared:用于进程间还是用于线程间
线程间:0
进程间:非0
value:初始化资源的数量
int sem_wait(sem_t *sem);
会对资源计数器进行-1操作
判断资源计数器的值是否大于0
是:接口返回
否:将线程放到PCB等待队列,阻塞起来
int sem_post(sem_t *sem);
会对资源计数器进行+1操作
判断资源计数器的值是否小于0
是:通知PCB等待队列
否:不通知PCB等待队列
int sem_destroy(sem_t *sem);
将开辟出来的sem的信号量释放掉
1、线程安全队列
互斥+同步
2、两种角色的线程
用到信号量需要包含一个头文件:
#include
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <pthread.h>
4 #include <semaphore.h>
5 #include <iostream>
6 #include <vector>
7
8 using namespace std;
9
10 #define CAPACITY 1
11 #define THREAD_NUM 1
12
13 class RingQueue
14 {
15 public:
16 RingQueue()
17 :vec_(CAPACITY)//初始化大小
18 {
19 capacity_ = CAPACITY;
20
21 //初始化互斥信号量
22 sem_init(&sem_lock_, 0, 1);
23 //初始化同步信号量
24 sem_init(&sem_read_, 0, 0);//刚开始只能读0
25 sem_init(&sem_write_, 0, CAPACITY);
26
27 pos_read_ = 0;//读写从下标0开始
28 pos_write_ = 0;
29 }
30
31 ~RingQueue()
32 {
33 //销毁互斥
34 sem_destroy(&sem_lock_);
35
36 //销毁读写
37 sem_destroy(&sem_read_);
38 sem_destroy(&sem_write_);
39 }
40
41 void Push(int data)//入队
42 {
43 sem_wait(&sem_write_);//进行写,保证同步
44
45 sem_wait(&sem_lock_);//保证互斥
46 vec_[pos_write_] = data;//在pos_write_位置写入
47 pos_write_ = (pos_write_ + 1) % capacity_;
48
W> 49 printf("i write %d, i am %p\n", data, pthread_self());
50
51 sem_post(&sem_lock_);//解锁
52
53 sem_post(&sem_read_);//通知读
54 }
55
56 void Pop(int* data)//出队
57 {
58 sem_wait(&sem_read_);//进行读,保证同步
59
60 sem_wait(&sem_lock_);//加锁
61 *data = vec_[pos_read_];//从pos_read_拿数据
62 pos_read_ = (pos_read_ + 1) % capacity_;
63
W> 64 printf("i read %d, i am %p\n", *data, pthread_self());
65
66 sem_post(&sem_lock_);//解锁
67
68 sem_post(&sem_write_);//通知写
69 }
70
71 private:
72 vector<int> vec_;//模拟的队列
73 int capacity_;//设定容量
74
75 //保证互斥的信号量
76 sem_t sem_lock_;
77 //保证同步的信号量
78 sem_t sem_write_;
79 sem_t sem_read_;
80
81 //标识读写的位置
82 int pos_read_;
83 int pos_write_;
84 };
85
86 void* ReadStart(void* arg)
87 {
88 RingQueue* rq = (RingQueue*)arg;
89 while(1)
90 {
91 int data;
92 rq->Pop(&data);
93 }
94 return NULL;
95 }
96
97 int g_data = 1;
98 sem_t sem_write_lock_;//给写加个互斥锁
99
100 void* WriteStart(void* arg)
101 {
102 RingQueue* rq = (RingQueue*)arg;
103 while(1)
104 {
105 sem_wait(&sem_write_lock_);
106 rq->Push(g_data++);
107 sem_post(&sem_write_lock_);
108 }
109 return NULL;
110 }
111
112 int main()
113 {
114 sem_init(&sem_write_lock_, 0, 1);
115
116 RingQueue* rq = new RingQueue();
117 if(rq == NULL)
118 {
119 return 0;
120 }
121
122 pthread_t read[THREAD_NUM], write[THREAD_NUM];
123 for(int i = 0; i < THREAD_NUM; i++)
124 {
125 int ret = pthread_create(&read[i], NULL, ReadStart, (void*)rq);
126 if(ret < 0)
127 {
128 perror("pthread_create fail\n");
129 return 0;
130 }
131 ret = pthread_create(&write[i], NULL, WriteStart, (void*)rq);
132 if(ret < 0)
133 {
134 perror("pthread_create fail\n");
135 return 0;
136 }
137 }
138
139 for(int i = 0; i < THREAD_NUM; i++)
140 {
141 pthread_join(read[i], NULL);
142 pthread_join(write[i], NULL);
143 }
144
145 delete rq;
146 sem_destroy(&sem_write_lock_);
147
148 return 0;
149 }
线程池的本质是:
线程池当中包含一个线程安全的队列+一大堆的线程
线程池当中的线程都是从线程池当中的线程安全队列当中获取元素进行处理
逻辑上属于消费线程
线程池当中的线程执行同样的入口函数,执行的是同样的代码
一个服务端后台的代码执行的业务是非常庞杂的,如果一个线程池只能处理一个类型的数据,则需要对每一个业务数据都创建一个线程池,
这无疑是消耗巨大的一件事
问:怎么让一个线程池可以处理多种多样的问题?
设计线程安全队列:
1、线程安全(互斥+同步)
互斥锁+条件变量
信号量
2、设计线程安全队列的元素类型
元素类型:待要处理的数据+处理该数据的方法(函数指针保存函数地址)
读写锁适用的场景
大量读,少量写
多个程序可以并行的对临界资源进行读操作,程序不会产生二义性结果
读写锁的三种模式:
以读模式打开读写锁
以写模式打开读写锁
不加锁
读写锁的内部由一个引用计数,来统计当前以读模式打开的读写锁的线程的数量
通过引用计数来判断当前读写锁是否还有线程以读模式打开
判断什么时候读写锁是空闲的,没有被占用
现在有一个线程A和一个线程B都通过读模式打开了,接着有一个线程C想通过写模式打开,又有一个线程D想通过读模式打开,此时AB已经拥有了读写锁,线程C阻塞,线程D能否拿到读写锁?
不能拿到,读写锁有一个保护机制,当有线程以读摸式打开,又有线程想以写模式打开时,此后的所有想以读模式打开的线程都会被阻塞
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
读方式
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
写方式
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);