1、一些基本概念
互斥:在同一时间内访问资源的唯一性
同步:是一种时序性和协作性
临界资源:一次仅允许一个进程使用的资源
临界区:访问临界资源的一段代码
2、为什么要进行同步?互斥?
线程互斥:因为多个线程是用共享一个资源的,这个资源就是临界资源,多个线程对资源的访问就需要用到线程的同步和互斥。
线程同步:如果我们的某一个线程优先级特别高,那么它可能一种占据这个临界资源,这个时候,其他线程也无法使用这个临界资源,所以这个时候就需要我们使用同步了。进程同步的意思就是,每次使用完一个资源后需要排队。
3、如何同步?
*使用了条件变量或者信号量保证同步。*
条件变量:
1)、概念:
条件变量就是等待某一个条件的发生,和信号一样
2)、用法:
条件变量使我们睡眠的等待某种条件的发生
条件变量是利用线程间共享全局变量进行同步的一种机制,主要包括两种动作:
- 一个线程等待“条件成立”而处于挂起状态
- 另外一个线程使条件成立。
-
为了防止竞争,条件变量的使用总是和互斥锁结合在一起的。
条件变量的类型是:pthread_cont_t;
3)、创建
条件变量有两种创建方式:
- 静态创建: pthread_cond_t cond =PTHREAD_CON D_INITIALIZER;
- 动态创建:调用pthread_cont_init()函数:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr)
cond_attr的值通常设为NULL;
4)注销:
pthread_cond_destory(pthread_cont_t *cond)
使用条件:只有 在没有 线程在该条件变量上等待的时候,才能 注销这个条件变量 ,否则返回EBUSY。
5)等待条件变量:
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);//在调用之前必须要先获得锁,(即调用之前是lock,调用之后是unlock)
且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
参数:第一个参数 指的是条件变量
第二个参数:指向互斥锁
6)唤醒条件变量等待线程:
int pthread_cond_broadcast(pthread_cond_t *cond);//将会激活所有的线程
int pthread_cond_signal(pthread_cond_t *cond);/将激活等待线程中的一个,调用了这个函数之后必须要释放锁
7)条件变量使用规范
- 等待条件代码:
pthread_mutex_lock(&mutex)
while(条件为假)
pthread_cond_wait(cond,mutex);
修改条件
pthread_mutex_unlock(&mutex)
- 给条件发送信号代码
pthread_mutex_lock(&mutex)
设置条件为真
pthrea_cond_signal(cond);
pthread_mutex_unlock(&mutex);
8)、条件变量为什么要和互斥锁搭配使用
首先,条件变量的作用是等待某一个条件的达成而处于挂起状态,而这个条件通常是多个线程或者进程的共享变量,所以对这个共享变量很有可能发生了竞争尤其还对这个共享变量添加了条件限制,所以必须对这个共享变量加上互斥锁。
信号量:
POSIX信号量和SystemV信号量作用相同,都用于同步操作,但是POSIX是用于线程间同步。
POSIX信号量和SystemV信号量的区别:
1)函数区分: 对于所有System V信号量函数,在它们的名字里面没有下划线。例如,应该是semget()而不是sem_get()。然而,所有的的POSIX信号量函数都有一个下划线。
2)System V的信号量一般用于进程同步, 且是内核持续的, api为
semget
semctl
semop
Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为
sem_open
sem_close
sem_unlink
Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为
sem_init
sem_destroy
POSIX信号量:
1)、初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数: 参数value 为信号量的初始值。参数pshared用于说明信号量的共享范围,如果pshared 为0,那么该信号量只能由初始化这个信号量的进程中的线程使用,如果pshared 非零,任何可以访问到这个信号量的进程都可以使用这个信号量
返回值 :0表示成功,其他表示失败。
2)、等待信号量
int sem_wait(sem_t *sem);
3)、发布信号量
int sem_post(sem_t *sem);
4)、信号量销毁
int sem_destory(sem_t *sem);
用posix信号量实现线程间同步,生产者消费者问题
分析:先给消费者信号量初始值置1,当消费者消费了以后发布信号给生产者让其生产,生产者接受这个消息后生产者开始生产,生产完毕后发布消息给消费者。
4、如何互斥?
使用互斥锁达到互斥的效果:
在使用互斥锁之前需要定义互斥锁(全局变量),定义形式如下:
pthread_mutex_t lock;
(1)、互斥锁的初始化
在pthread_mutex_init()函数中:
1)、int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
- 参数:
mutex:是指向要初始化的互斥量的指针(全局变量)
attr:是指向属性对象的指针,一般使用NULL
2)可以通过宏PTHREAD_MUTEX_INITIALIZER来初始化静态分配的互斥锁 :
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER
对于静态初始化的互斥锁,不需要调用pthread_mutex_init函数。
(2)、互斥锁的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
(3)、锁的操作
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁操作,如果获取不到资源就处于挂起状态(阻塞的)
int pthread_mutex_trylock(pthread_mutex_t *mutex);//获取不到锁资源,立即报错 (非阻塞的)
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁操作
通常是以下过程:
1)、初始化互斥锁
2)、加锁(申请)
3)、对临界资源的操作
4)、解锁(在任何可能退出的地方都要有解锁操作)
(4)、死锁问题
死锁主要发生在有多个依赖锁形成时,如何避免死锁时使用互斥量要格外注意的东西。
造成死锁的四个必要条件:
- 互斥条件:资源是独占且排他使用,即任意时刻资源只能给一个进程使用
- 不剥夺条件:进程所获得的资源在使用完毕之前,不能被其他进程强行剥夺,只能由该进程释放
- 请求与保持条件:进程每次申请它所需要的一部分资源,在申请新资源的同时,继续占优已经分配的资源
- 循环等待在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所申请地资源。
(5)、避免死锁:
- 对共享资源操作前一定要获得锁
- 完成操作时,一定要释放锁
- 尽量短时间的占用锁
- 如果有多个锁,如果获得的顺序是ABC,那么释放的顺序也应该是ABC
- 线程错误返回时,应该释放它所有的锁