线程的同步与互斥
在上一篇博客中我曾经提到,在Linux下多线程是共享数据的,但是共享数据可能会发生访问的冲突。比如两个线程都要把某个全局变量增加1,这个操作一般需要三条指令完成:
①从内存读变量值到寄存器
②寄存器的值加1
③将寄存器的值写回内存
假设两个线程在多处理器平台下同时执行者三条指令,很有可能会导致变量只加了一次而不是两次。
我们测试一下:
创建两个线程,各自将count增加5000次,正常情况下count的值应该是10000,但是事实上却是每一次的运行结果都不一定一样,有时比5000多,有时可能都到6000。
运行后的结果是
由此可见,对于多线程的访问,这里冲突了。
互斥:
从某种意义上来说,互斥就是一份资源在同一时刻只能被一个线程访问。通俗点说就是一把钥匙对应这一个门,而一次只能有一个人拿着钥匙去开门,而在开完门后门内的资源就一直被占据,其余的人想要进门就必须得等待那个人出来,这就是所谓的互斥。
同步:
同不就是进程按照一定的顺序访问临界资源,同步强调的是协同。通俗点说就是门外的人按照一定的顺序来们进行访问。
事实上,对于多线程的程序来说,访问冲突是非常普遍的,解决的方法就是引入互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读——修改——写”的操作,然后释放锁给其他的线程。没有获得锁的线程就只能等待而不能访问共享数据,这样“读——修改——写”三步操作组成一个原子操作,要么都执行,要么都不执行,而不会执行到一半被打断。
互斥锁操作函数
mutex用phread_mutex_t类型的变量表示。
初始化和销毁
我们可以看到,初始化有两种方式:
①调用函数pthread_mutex_init。参数mutex为在外面定义的一个pthread_mutex_t数据类型的数据指 针,init函数调用完成后,互斥锁的值会放在mutex指向的内存单元。要用默认的属性初始化互斥量,就将参数2attr设为NULL 。
②直接定义一个互斥锁,用下面宏定义(PTHREAD_MUTEX_INITIALIZER)给它赋值。此方式相当于方式①的attr设为NULL。即默认的属性初始化互斥量。
加锁解锁
其中lock与trylock都是加锁,区别在于lock是以阻塞式等待,而trylock是以非阻塞式等待。当一个线程已经占用锁的资源时,其他线程就会因为得不到锁资源而被阻塞,这时就需要使用trylock进行加锁了,如果调用时互斥量处于未锁住状态,那么pthread_mutex_trylock将会锁住该信号量,不会出现阻塞直接返回0,否则就会trylock失败,返回EBUSY。
下面我们再演示一下之前的代码,这次加上锁之后看得到的结果是不是10000.
运行后得到的结果是:
可以看到结果是10000.说明锁的作用体现了。
补充:死锁
什么是死锁?
在多任务系统下,当一个或多个进程等待系统资源,而资源又被进程本身或其它进程占用时,就形成了死锁。还用上面的例子解释就是有人拿了钥匙,但是他忘了自己已经拿了钥匙,然后就一直与其他人一样等着。这时就会出现死锁。
常见的死锁出现的原因:
①同一线程申请两次同样的锁资源
②两个线程互相申请彼此占有的锁资源
死锁产生的必要条件:
①请求与保持
②互斥属性
③不可剥夺、抢占
剥夺:Linux进程线程是可剥夺的(当时间片结束,系统强行剥夺,运行其他程序)
抢占:当一个进程或线程比正在运行的进程或线程的优先级高,则可发生抢占,Linux支持
④环路等待
如何避免死锁:
破坏任意4个产生条件中的一个就可以破坏死锁。