互斥量:多个线程同时访问共享数据时可能会冲突,这跟信号的可重性是同样的问题。如 果两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:
1. 从内存读变量值到寄存器
2. 寄存器的值加1
3. 将寄存器的值写回内存
先举个例子:创建两个线程,各把counter增加5000次,正常情况下最后counter应该等于10000。
代码实现如下:
结果:
可以看到,每次运行程序的结果都不一样。说明在调用过程中发生了互斥现象。
解决办法:加互斥锁
实现多线程同步可以引互斥锁(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待不能访问共享数据,这样“读-修改-写”三步操作组成个原操作,要么都执,要么都不执,不会执到中间被打断,也不会在其它处理器上并做这个操作。 Mutexpthread_mutex_t类型的变量表,可以这样初始化和销毁。
相关函数如下:
pthread_mutex_init函 数初始化的Mutex可以pthread_mutex_destroy销毁。 如果Mutex变量是静态分配的(全局变量 或static变量),也可以宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于 pthread_mutex_init初始化并且attr参数为NULL。 Mutex的加锁和解锁操作可以下列函数
一个线程可以调pthread_mutex_lock获得Mutex,如果这时另一个线程已经调pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执。如果这个线程既想获得锁,又不想挂起等待,可以调pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,不会使线程挂起等待。
现在给上一个例子加上互斥锁,代码如下:
运行结果如下:
可以看到,加锁后实现了线程同步。
死锁原理:
根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。
死锁的四个必要条件:1、互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
2、请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。3、非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。4、循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源解决死锁的基本方法:
1、预防死锁:
资源一次性分配:(破坏请求和保持条件)
可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)。
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)。
2、避免死锁:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
3、检测死锁
首先为每个进程和每个资源指定一个唯一的号码;
然后建立资源分配表和进程等待表。
4、解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。