Linux下的线程安全
线程安全:多个执行流对临界资源进行争抢访问,而不会造成数据二义性和逻辑混乱,成这段代码的过程是线程安全的。
实现:保证多个执行流对临界资源进行争抢访问不造成数据二义性。
同步与互斥:
同步:通过条件判断,实现对灵界资源访问的时序合理性。
互斥:通过唯一访问,实现对临界资源的安全性。
一、互斥实现的技术:互斥锁
原理:保证同一时间只有一个执行流对临界资源进行访问。
即:对临界资源进行标记,无访问时标记为1,有访问时标记为0,当标记为1时,则用户可访问或线程可访问;当标记为0时,线程不可访问。
先进行判断,能访问则访问,不能访问则休眠。
1.1 互斥锁:
mutex:是一个计数器。
互斥锁的代码操作流程:
定义互斥锁变量: pthread_metux_t mutex;
初始化互斥锁:
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t* attr);
mutex :互斥索变量首地址;
attr : 互斥锁属性,通常置为NULL。
对临界资源访问时先加锁:
两种方式:
阻塞加锁:int pthread_mutex_lock(pthread_mutex_t* mutex);
非阻塞加锁:int pthread_mutex_trylock(pthread_mutex_t* mutex);
访问结束,记得解锁:
int pthread_mutex_unlock(pthread_mutex_t* mutex);
不用锁了,释放资源,销毁互斥锁:
int pthread_mutex_destory(pthread_mutex_t* mutex);
注意事项:
(1) 加锁保护区域最好只是对临界资源进行加锁,因为保护的越多,执行所需要的时间越长,降低效率。
(2)加锁之后在任意有可能退出线程的地方都要进行解锁操作,若没有解锁直接退出,会造成其他线程访问锁时的状态。
2. 死锁:多个执行流对临界资源争抢访问时,因推进顺序不当,多条执行流相互等待,最终程序无法继续执行。
必要条件:必须具备的条件,如果不具备就无法实现死锁。
①互斥条件:一个资源只能被一个执行流进行访问。
②不可剥夺条件:谁加的锁谁才能进行解锁操作,一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
③请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
④环路等待条件:线程1抢到锁A,去抢锁B,线程2抢到锁B去抢锁A。形成环路,进而产生死锁。
避免死锁:
①破坏死锁的四个必要条件
②加锁顺序一致
③避免锁未释放的场景
④资源一次性分配
二、同步实现技术:条件变量/信号量
2.1 条件变量:
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,**直到(直到后面跟着的就是条件变量)**其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
实现同步的思路是向用户提供两个接口(一个是让进程陷入阻塞等待的接口,一个是唤醒休眠的接口)+pcb等待队列。
条件变量的接口实现:
定义条件变量:pthread_cond_t cond;
初始化条件变量:int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr);
在不满足资源访问时:int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
//cond :条件变量 mutex: 互斥量
线程促使资源访问条件满足之后:
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒全部
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个
销毁:int pthread_cond_destory(pthread_cond_t* cond);
操作流程:
(1)加锁;
(2)用户自己进行条件判断,不能访问则调用pthread_cond_wait()陷入等待。
(3)别唤醒之后,能够访问,则访问数据,获取资源。
(4)唤醒生产资源的线程。
(5)加锁。
注意事项:
(1) pthread_cond_wait() 实现了三步操作:先解锁,陷入休眠,被唤醒后再加锁。
(2)用户自己进行的判断需要用到while循环判断。
(3)不同的角色应该等待在不同的条件变量上。
2.2 信号量:
强调:不能与信号概念混淆。
作用:实现线程间的同步与互斥。
本质:一个计数器+pcb等待序列。
同步与互斥:
(1)实现同步:计数器对资源数量进行计数,当线程想要获取资源的时候,先访问信号量,判断是否能够获取资源(通过计数器本身),若计数器《=0,则直接堵塞线程,计数器-1;当其他线程生产资源的时候,计数器+1,唤醒等待队列上的pcb。
(2)实现互斥:保证计数器不大于1,就表示每次最多只有一个线程能够访问资源。
定义信号量sem_ t sem;
初始化信号量int sem_ init(sem_ t *sem, int pshared, int value)
在访问临界资源之前,先判断计数,是否能够访问资源,若不能访问,则阻塞线程;若可以访问则调用直接返回
int sem_ wait(sem_ t *sem) / int sem trywait(sem_ t *sem)/ int sem timedwait(sem_ t *sem, struct timespec *ts)
访问临界资源之后/生产资源之后,唤醒一个等待的线程,并且计数+ 1
int sem_ post(sem_ t *sem);
不使用信号量记得释放资源
int sem_ destroy(sem. _t *sem);
Linux下的线程安全
线程安全:多个执行流对临界资源进行争抢访问,而不会造成数据二义性和逻辑混乱,成这段代码的过程是线程安全的。
实现:保证多个执行流对临界资源进行争抢访问不造成数据二义性。
同步与互斥:
同步:通过条件判断,实现对灵界资源访问的时序合理性。
互斥:通过唯一访问,实现对临界资源的安全性。
一、互斥实现的技术:互斥锁
原理:保证同一时间只有一个执行流对临界资源进行访问。
即:对临界资源进行标记,无访问时标记为1,有访问时标记为0,当标记为1时,则用户可访问或线程可访问;当标记为0时,线程不可访问。
先进行判断,能访问则访问,不能访问则休眠。
1.1 互斥锁:
mutex:是一个计数器。
互斥锁的代码操作流程:
定义互斥锁变量: pthread_metux_t mutex;
初始化互斥锁:
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t* attr);
mutex :互斥索变量首地址;
attr : 互斥锁属性,通常置为NULL。
对临界资源访问时先加锁:
两种方式:
阻塞加锁:int pthread_mutex_lock(pthread_mutex_t* mutex);
非阻塞加锁:int pthread_mutex_trylock(pthread_mutex_t* mutex);
访问结束,记得解锁:
int pthread_mutex_unlock(pthread_mutex_t* mutex);
不用锁了,释放资源,销毁互斥锁:
int pthread_mutex_destory(pthread_mutex_t* mutex);
注意事项:
(1) 加锁保护区域最好只是对临界资源进行加锁,因为保护的越多,执行所需要的时间越长,降低效率。
(2)加锁之后在任意有可能退出线程的地方都要进行解锁操作,若没有解锁直接退出,会造成其他线程访问锁时的状态。
2. 死锁:多个执行流对临界资源争抢访问时,因推进顺序不当,多条执行流相互等待,最终程序无法继续执行。
必要条件:必须具备的条件,如果不具备就无法实现死锁。
①互斥条件:一个资源只能被一个执行流进行访问。
②不可剥夺条件:谁加的锁谁才能进行解锁操作,一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
③请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
④环路等待条件:线程1抢到锁A,去抢锁B,线程2抢到锁B去抢锁A。形成环路,进而产生死锁。
避免死锁:
①破坏死锁的四个必要条件
②加锁顺序一致
③避免锁未释放的场景
④资源一次性分配
二、同步实现技术:条件变量/信号量
2.1 条件变量:
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,**直到(直到后面跟着的就是条件变量)**其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
实现同步的思路是向用户提供两个接口(一个是让进程陷入阻塞等待的接口,一个是唤醒休眠的接口)+pcb等待队列。
条件变量的接口实现:
定义条件变量:pthread_cond_t cond;
初始化条件变量:int pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* attr);
在不满足资源访问时:int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
//cond :条件变量 mutex: 互斥量
线程促使资源访问条件满足之后:
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒全部
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个
销毁:int pthread_cond_destory(pthread_cond_t* cond);
操作流程:
(1)加锁;
(2)用户自己进行条件判断,不能访问则调用pthread_cond_wait()陷入等待。
(3)别唤醒之后,能够访问,则访问数据,获取资源。
(4)唤醒生产资源的线程。
(5)加锁。
注意事项:
(1) pthread_cond_wait() 实现了三步操作:先解锁,陷入休眠,被唤醒后再加锁。
(2)用户自己进行的判断需要用到while循环判断。
(3)不同的角色应该等待在不同的条件变量上。
2.2 信号量:
强调:不能与信号概念混淆。
作用:实现线程间的同步与互斥。
本质:一个计数器+pcb等待序列。
同步与互斥:
(1)实现同步:计数器对资源数量进行计数,当线程想要获取资源的时候,先访问信号量,判断是否能够获取资源(通过计数器本身),若计数器《=0,则直接堵塞线程,计数器-1;当其他线程生产资源的时候,计数器+1,唤醒等待队列上的pcb。
(2)实现互斥:保证计数器不大于1,就表示每次最多只有一个线程能够访问资源。
定义信号量sem_ t sem;
初始化信号量int sem_ init(sem_ t *sem, int pshared, int value)
在访问临界资源之前,先判断计数,是否能够访问资源,若不能访问,则阻塞线程;若可以访问则调用直接返回
int sem_ wait(sem_ t *sem) / int sem trywait(sem_ t *sem)/ int sem timedwait(sem_ t *sem, struct timespec *ts)
访问临界资源之后/生产资源之后,唤醒一个等待的线程,并且计数+ 1
int sem_ post(sem_ t *sem);
不使用信号量记得释放资源
int sem_ destroy(sem. _t *sem);
举报