1 互斥
1.1 互斥要做的事情
控制线程的访问时序。但多线程能够同时访问到临界资源的时候,有可能会导致线程执行的结果产生二义性。
而互斥就是要保证多个线程在访问同一个临界资源,执行临界区代码的时候(非原子性操作,线程可以被打断)控制访问时序。
让一个线程独占临界资源执行完,再让另一个独占执行。
临界资源:能够被多个线程访问到的资源,称之为临界资源。
临界区代码:访问临界资源的代码。
1.2 如何做到控制线程访问时序(互斥锁)
互斥锁的原理:
互斥锁的本质就是0/1计数器,计数器的取值只能是0或者1;
计数器的值为1的时候:表示当前线程可以获取到互斥锁,而去访问临界资源;
计数器的值为0的时候:表示当前线程不可以获得到互斥锁,从而不能访问临界资源。
加锁:加锁成功的时候,会将计数器的值从1改成0;
解锁:解锁成功的时候,会将计数器的值从0改成1。
多个线程要执行临界区当中的代码时候为了保证互斥,都是需要先进行加锁保护的。
极端情况下,多个线程同时进行加锁操作,只有一个线程可以枷锁成功,其他线程加锁失败。
1.3 互斥锁的计数器当中如何保证原子性
为什么计数器当中的值从0->1,或者1->0是原子性的呢?
操作不可被分割;要么开始要么没有开始不存在中间状态。
直接使用寄存器当中的值和计数器内存的值交换,而交换时一条汇编指令就可以完成的。
加锁的时候:寄存器当中的值设置为(0),寄存器与计数器当中的值进行交换。
第一种情况:计数器的值为1,说明锁空闲,没有被线程加锁
第二种情况:计数器的值为0,说明锁忙碌,被其他的线程加锁拿走
解锁的时候:寄存器当中的值设置为(1),寄存器与计数器当中的值进行交换。
1.4 互斥锁的接口
1.4.1 初始化 pthread_mutex_t mutex;
动态初始化:
int pthread_mutex_init ( pthread_mutex_t *restrict mutex , const pthread_mutexattr_t *restrict arrt ) ;
参数:
pthread_mutex_t:互斥锁类型(结构体)
attr:互斥锁的属性,一般直接传递NULL
静态初始化: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1.4.2 加锁 1->0
int pthread_mutex_lock ( pthread_mutex_t *mutex ); --阻塞加锁
int pthread_mutex_trylock ( pthread_mutex_t *mutex ); --非阻塞加锁,搭配循环使用判断是否加锁成功
int pthread_mutex_timelock ( pthread_mutex_t *restrict mutex , const struct timespec *restrict abs_timeout ); --带有超时时间的加锁接口
struct timespec {
tim_t tv_sec; // secods 5
long tv_nsec; // and nanoseconds
};
1.4.3 解锁 0->1
int pthread_mutex_unlock ( pthread_mutex_t *mutex );
线程所有可能退出的地方都进行解锁!
互斥:可以保证多个线程对于临界资源进行访问的互斥属性。
如果只保证互斥,有可能出现“线程饥饿”问题。
线程同步:在保证互斥的前提下,保证多个线程对临界资源访问的合理性。
1.4.4 销毁
int pthread_mutex_distory ( pthread_mutex *mutex );
1.4.5 使用
代码如下,大意为两个工作线程同时抢票,一共一百张,若没有加入互斥锁,俩线程可能出现二义性,造成线程不安全的情况,而加入互斥锁之后就不存在二义性问题。
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<unistd.h>
4 int g_tickets=100;
5 pthread_mutex_t g_lock;
6 void* mythread_start(void* arg){
7 while(1){
8 pthread_mutex_lock(&g_lock);
9 if(g_tickets<=0){
10 pthread_mutex_unlock(&g_lock);
11 break;
12 }
13 printf("i am %p,i hace tickets %d\n",pthread_self(),g_tickets);
14 g_tickets--;
15 pthread_mutex_unlock(&g_lock);
16 }
17 return NULL;
18 }
19 int main(){
20 pthread_mutex_init(&g_lock,NULL);
21 pthread_t tid[2];
22 int i;
23 for(i=0;i<2;i++){
24 int ret=pthread_create(&tid[i],NULL,mythread_start,NULL);
25 if(ret<0){
26 perror("pthread_create\n");
27 return 0;
28 }
29 }
30 for(i=0;i<2;i++)
31 {
32 pthread_join(tid[i],NULL);
33 }
34 pthread_mutex_destroy(&g_lock);
35 return 0;
36 }
2 同步
2.1 条件变量
使用原理:线程在加锁之后,判断下临界资源是否可用
如果可用,则直接访问临界资源
如果不可用,则调用等待接口,让该线程进行等待
条件变量的原理:
本质上是PCB等待队列(存放在等待的线程PCB)
2.2 接口
2.2.1 初始化 pthread_cond_signal
动态初始化:
int pthread_cond_init ( pthread_cond_t *restrict cond , const pthread_condattr_t *restrict attr );
参数:
pthraed_cond_t:条件变量的类型
pthread_condattr_t:NULL,采用默认属性
int pthread_cond_destroy ( pthread_cond_t *cond );
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.2.2 等待接口
int pthread_cond_wait ( pthread_cond_t *restrict cond , pthread_mudex_t *restrict mutex);
作用:谁调用将谁放倒PCB等待队列
参数
cond:条件变量
mutex:互斥锁
int pthread_cond_timedwait ( pthread_cond_t *retrict cond , pthread_mutex_t *restrict mutex , const struct timespec *restrict abstime );
2.2.3 唤醒接口
int pthread_cond_broadcast ( pthread_cond_t *cond ); // 唤醒所有
int pthread_cond_signal ( pthread_cond_t *cond ); // 唤醒至少一个线程
2.3 条件变量的夺命追问
(1)条件变量的扽得改接口第二个参数为什么会有互斥锁?
在函数内部需要解锁操作,如果不解锁就阻塞等待,则其他线程一定拿不到锁。
(2)pthread_cond_wait的内部是针对互斥锁做了什么操作?先释放互斥锁还是先将线程放入到PCB等待队列。
解锁操作;先放入PCB等待队列。
(3)线程被唤醒之后会执行什么代码,需要再获取互斥锁么?
从PCB等待队列当中移出;抢锁。