线程怎么实现同步
前文有提到,一个进程中的各个线程都有共享的资源而且是完全开放的,那么在进程运行中会出现多个线程访问同一个公共资源的问题。
这种现象我们称之为线程之间产生了资源竞争,这种竞争会导致程序异常甚至崩溃。
Linux提供了相应的解决方案,来确保每个线程能同步的访问进程提供的公共资源,线程同步。简而言之,就是排排队大家一个一个来!
线程同步四种方法:
- 互斥锁
- 信号量
- 条件变量
- 读写锁
互斥锁:
又称互斥体或互斥量,是最简单的一种线程同步机制,顾名思义,当一个线程访问的时候,就会把资源“锁”上,直到访问结束才会解锁,给其他线程访问。
互斥锁本身就是一个全局变量:unlock 和 lock
- ”unlock“表示当前资源可以访问,第一个访问资源的线程将互斥锁修改为”lock“,访问完以后再修改为”unlock“
- "lock"表示线程正在访问资源,其他线程需要等待互斥锁的值为"unlock"才能继续访问
解铃还须系铃人,该线程负责的加锁,解锁也需要该线程
信号量:
又称信号灯,控制同时访问公共资源的线程数量,当线程数量小于等于1时,这种信号量可以叫二元信号量,同理多的时候,叫多元信号量,是指同一时刻最后只有这么多个线程可以访问该资源
信号量的取值范围必须>=0;值得一提的是信号量可以执行加一和减一的操作,而且这种操作还是原子操作来实现的。原子操作,你可以理解为多个线程修改信号量,但是在修改值的过程中互不干扰。
具体操作流程:
- 信号量不能小于零
- 线程访问资源时,信号量减一,访问完成加一。
- 信号量为0时候,其他访问线程需要等待,知道大于0
信号量分类:二元信号量,计数信号量
- 二元信号量初始值为1,信号量的值只有0和1,一定程度上替代互斥锁进行线程同步。
- 计数信号量,初始值大于1,可以允许多个线程同一时间访问同一资源,起到限制访问个数的作用。
条件变量:
功能类似现实中的门,只有打开和关闭两种状态,对应条件中的成立与不成立两种判定,一旦关闭,所有线程都不得访问该资源,一旦打开,那就恢复执行。通常条件变量和互斥锁是搭配使用的。
条件变量的本质也是全局变量,它的功能是阻塞线程,直到收到条件成立的信号,被阻塞的程序才能继续执行。
具体流程:
- 阻塞线程,知道收到信号
- 向等待队列中一个或者所有线程发送条件成立的信号,解除被阻塞的状态。
为了避免多线程抢资源的情况发生,条件变量必须和互斥锁搭配使用。
读写锁:
如果很多线程只是进行读取操作,只有少部分是写操作(修改),可以使用读写锁。
读写锁的核心思想是将线程访问共同数据发出的请求分类:
- 读请求:只读,不修改共享数据
- 写请求: 存在修改共享数据的操作
当有多个读线程的时候,他们可以同时访问,但是写线程就必须要等他们读完才能访问,反过来也是,只不过写线程必须要一个一个来访问。读线程访问时候,读写锁称之为读锁,写线程访问时候,读写锁称之为写锁。
死锁
死锁:线程一直被阻塞的情况
比如:给线程加上互斥锁但是忘了解锁,那就会出现一直阻塞的情况。
避免死锁的建议:
-
使用互斥锁,信号量,条件变量,读写锁的时候
(1)占用互斥锁的进程要及时解锁
(2)通过sem_wait()函数占用信号量资源的线程,及时调用sem_post()函数进行释放
(3)当线程phtread_cond_wait()函数被阻塞时,一定要保证有其他线程唤醒此线程
(4)无论线程占用的是读锁还是写锁,都要及时解锁 -
POSIX标准中,很多阻塞线程的函数都提供两个版本tryxxx(),一种是timexxx();其中try是不会阻塞线程,time不会一直阻塞线程,多使用这两种可以大大降低死锁的概率。(pthread_mutex_lock,pthread_mutex_trylock)
(sem_wait,sem_trywait)(pthread_cond_wait,pthread_cond_timewait) -
多线程程序中,多个线程申请资源的顺序最好一致,比如线程1先申请matex1在申请matex2,而线程2先申请matex2,再申请matex1,就会发生顺序不一致导致的死锁。