一、为什么需要线程同步?
1.对共享资源进行保护。
这里的共享资源是指多个线程都会进行访问的资源。
2.解决数据一致性问题。
当一个线程可以修改的变量,其它的线程也可以读取或者修改的时候,这个时候就存在数据一致性的问题,需要对这些线程进行同步操作,确保它们在访问变量的存储内容时不会访问到无效的值。本质在于进程中的多个线程对共享资源的并发访问(同时访问)。
二、线程同步机制
1.互斥锁
在访问共享资源前对互斥锁进行上锁,在访问完成后释放互斥锁(解锁);互斥锁上锁之后,其它线程试图再次加锁都会被阻塞,直到当前线程释放互斥锁。
在我们的程序设计当中,只有将所有线程访问共享资源都设计成相同的数据访问规则,互斥锁才能正常工作。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都申请锁,也还是会出现数据不一致的问题。
互斥锁的基本操作
(1)初始化
互斥锁使用 pthread_mutex_t 数据类型表示,在使用互斥锁之前,必须首先对它进行初始化操作,可以使用两种方式对互斥锁进行初始化操作。
(2)加锁和解锁
2.条件变量
条件变量用于自动阻塞线程,直到某个特定事件发送或某个条件满足为止。通常情况下,条件变量是和互斥锁一起搭配使用的。使用条件变量主要包括两个动作:
⚫ 一个线程等待某个条件满足而被阻塞;
⚫ 另一个线程中,条件满足时发出“信号”。
例如:条件变量可以允许一个线程休眠(阻塞等待)直至获取到另一个线程的通知(收到信号)再去执行自己的操作,这样避免了该线程无限次访问是否满足条件而占用CPU资源。
条件变量操作:
(1)初始化
(2)通知和等待条件变量
3.自旋锁
自旋锁与互斥锁相似,但是互斥锁在无法获取到锁时会让线程陷入阻塞状态;而自旋锁在无法获取到锁时将会在原地“自旋”(调用者一直在循环查看该自旋锁的持有者是否已经释放了锁)等待。
自旋锁的缺点:
(1)占用CPU,处于“自旋”的线程一直在占用CPU运行而迟迟得不到锁。
(2)对同一自旋锁加锁两次必然会导致死锁。
因此我们要谨慎使用自旋锁,自旋锁通常用于以下情况:需要保护的代码段执行时间很短,这样就会使得持有锁的线程会很快释放锁,而“自旋”等待的线程也只需等待很短的时间;在这种情况下就比较适合使用自旋锁,效率高!
基本操作:
a.初始化
b.自旋锁加锁和解锁
4.读写锁(共享互斥锁)
读写锁有3 种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和不加锁状态(见),一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。因此可知,读写锁比互斥锁具有更高的并行性!
读写锁有如下两个规则:
⚫ 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
⚫ 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。
ps:读写锁非常适合于对共享数据读的次数远大于写的次数的情况。
基本操作:
(1)初始化
(2)上锁和解锁