一、线程同步是什么?和线程通信有什么区别?
线程同步和线程的通信是两种概念,线程之间可以进行通信去访问共享内存或者全局变量,如果多个线程都会对同一个变量或者资源进行访问吗,就需要对该变量或者资源进行保护,否则可能会出现异常。所以线程同步的目的就是为了对共享资源进行保护,确保访问对象是安全的。
二、线程同步-互斥锁
1.1 互斥锁
1.1 说明
互斥锁正如其名字来说就是一把锁,在访问共享资源时候对资源进行加锁操作,访问完成进行开锁释放资源的操作。A线程对某个资源进行
访问期间对资源加了锁,这时候A资源就持有这个锁的钥匙,这个时候B线程或者其他任何线程都无法访问这块资源,只有等A线程访问完成开锁释放了资源才可以。
对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒,它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能再次陷入阻塞,等待下一次解锁
1.2 互斥锁的使用方式
1. 初始化
初始化方式 | |
---|---|
使用宏初始化 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER |
使用初始化函数初始化 | int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); |
两种初始化方式皆可,区别在于,使用宏初始化的时候,只能在定义互斥锁时候进行初始化,使用函数初始化则可以在任意时候初始化,更为自由。
mutex是个指向需要初始化的互斥量对象
使用函数进行初始化时候如果attr参数不进行设置默认为NULL的话效果和使用宏初始化是一样的初始化方式如下:
申请变量后调用接口进行初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
2. 加锁和解锁
#include <pthread.h>
// 互斥锁加锁
pthread_mutex_lock();
//中间为共享资源
//互斥锁解锁
pthread_mutex_unlock();
3. 互斥锁销毁
当互斥锁完全不需要后,需要对互斥锁进行销毁以释放系统资源。互斥锁一旦被销毁就无法对齐上锁或者解锁操作,需要再次调用
init初始化才可以。
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
1.3 死锁
1. 什么是死锁?
死锁指的是两个以上的线程或者进程在争抢资源时候相互等待造成了系统阻塞,一直停留在等待状态,代码无法继续运行下去的一种现象
2. 死锁发生的原因
多线程:线程A和线程B互相持锁,A线程对mut1持锁,mut1在B线程释放,B线程对mut2持锁,mut2在线程1释放。
这时候试图访问A线程的B被阻塞了,试图访问B的A也被阻塞了
单线程:线程A持有了锁mut1还继续对mut1加锁,自己把自己阻塞了。
3. 如何避免死锁?
【1】多个线程枷锁顺序应该一致,比如有两个共享的全局变量,线程A,B都对他们进行访问,线程A先对1访问再对2访问,线程B相反
线程A访问1期间线程B被阻塞了,线程A此时还可以继续运行访问资源2,如果再访问2期间2已经被线程B枷锁了,这时候线程A就会也
被阻塞导致两个线程都在互相等待,系统被block了。
线程A
pthread_mutex_lock(mutex1);
pthread_mutex_lock(mutex2);
线程B
pthread_mutex_lock(mutex2);
pthread_mutex_lock(mutex1);
【2】单个线程要避免对同一个互斥锁进行多次加速导致自身死锁了。可以通过调用错误检查避免
【3】设置pthread_mutexattr_init(pthread_mutexattr_t *attr))函数的attr对象
PTHREAD_MUTEX_NORMAL | 标准互斥锁类型 |
PTHREAD_MUTEX_ERRORCHECK | 带错误检查的互斥锁 |
PTHREAD_MUTEX_RECURSIVE | 递归互斥锁,需要加锁数目等于解锁数目 |
PTHREAD_MUTEX_DEFAULT | 带错误检查的互斥锁 |