一、互斥锁
二、死锁
- 1. 预防死锁
- (1)破坏互斥条件
- (2)破坏不剥夺条件
- (3)破坏请求和保持条件
- (4)破坏循环等待条件
- 2. 避免死锁
- 3. 解除死锁
三、信号量
四、条件变量
一、互斥锁
- 互斥锁是线程同步最常用的一种方式,可以解决多线程访问共享资源数据混乱的问题。
- 通过互斥锁可以锁定一个代码块,被锁定的这个代码块,所有的线程只能"顺序执行"。
- 但执行效率降低,默认情况下临界区是可以被多个线程并行处理的,现在只能串行处理。
- 一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。
#include
#include
// 互斥锁
class locker
{
public:
// 初始化互斥锁
locker()
{
if (pthread_mutex_init(&m_mutex, NULL) != 0)
{
throw std::exception();
}
}
// 释放互斥锁资源
~locker()
{
pthread_mutex_destroy(&m_mutex);
}
// 加锁
// 若锁没有被锁定,此线程加锁成功,锁中记录该线程加锁成功。
// 若锁被锁定,其他线程会加锁失败,都会阻塞在这把锁上。
// 锁被解开后,阻塞在锁上的线程就解除阻塞,并且通过竞争的方式对锁进行加锁,没抢到锁的线程继续阻塞。
bool lock()
{
return pthread_mutex_lock(&m_mutex) == 0;
}
// 解锁
// 哪个线程加的锁,哪个线程才能解锁!
bool unlock()
{
return pthread_mutex_unlock(&m_mutex) == 0;
}
// 获取互斥锁地址
pthread_mutex_t *get()
{
return &m_mutex;
}
private:
// 互斥锁
pthread_mutex_t m_mutex;
};
二、死锁 1. 预防死锁
- 死锁的产生必须满足四个必要条件:互斥条件、不剥夺条件、请求和保持条件、循环等待条件。
(1)破坏互斥条件
- 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。
- 解决方法:将临界资源改造为可共享使用的资源,如:SPOOLing技术。
- 缺点:一般情况下为了系统安全,必须保护这种互斥性,可行性不高。
(2)破坏不剥夺条件
- 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 解决方法:
1. 申请的资源得不到满足时,立即释放拥有的所有资源。
2. 申请的资源被其他进程占用,考虑优先级由 *** 作系统剥夺资源。
- 缺点:
1. 实现复杂。
2. 释放已获得的资源可能造成前一阶段工作的失效。
3. 反复申请和释放资源会增加系统开销。
4. 可能导致进程饥饿。
(3)破坏请求和保持条件
- 请求和保持条件:进程已经保持至少一个资源,且又提出新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对已有的资源保持不放。
- 解决方法:进程在运行前,就一次分配所有需要的资源。
资源未满足前不能运行,运行后这些资源就一直归它所有。
- 缺点:
1. 有些资源可能只需要用很短的时间,如果进程的整个运行期间都一直保持着所有资源,资源利用率极低。
2. 可能导致进程饥饿。
(4)破坏循环等待条件
- 循环等待条件:存在进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
- 解决方法:给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,编号相同的资源一次申请完。
- 原理:在任何一个时刻,总有一个进程拥有的资源编号是最大的,那么这个进程申请之后的资源必然畅通无阻。
- 缺点:
1. 不方便增加新的设备,因为可能需要重新编号。
2. 进程实际使用资源的顺序可能和编号递增顺序不一致,导致资源浪费。
3. 编程不方便。
2. 避免死锁
银行家算法与安全性算法
3. 解除死锁- 死锁进程:死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程。
- 解决方法
1. 资源剥夺法:挂起某些死锁进程,将其资源分配给其他的死锁进程,应防止被挂起的进程长时间得不到资源而饥饿。
2. 撤销进程法:强制撤销死锁进程,剥夺这些进程的资源。
若有些进程可能已经运行了很长时间,被终止功亏一篑。
3. 进程回退法:让死锁进程回退到足以避免死锁的地步。
三、信号量
- 信号量不一定是锁定某一个资源,也可以是流程上的概念。
一个线程完成了某一个动作就通过信号量通知别的线程,别的线程再进行某些动作。
- 信号量主要阻塞线程,不能完全保证线程安全,如果要保证线程安全,需要信号量和互斥锁一起使用。
#include
#include
// 信号量
class sem
{
public:
// 初始化信号量
sem()
{
// 用于线程同步,当前信号量拥有的资源数为0
if (sem_init(&m_sem, 0, 0) != 0)
{
throw std::exception();
}
}
// 初始化信号量
sem(int num)
{
// 用于线程同步,当前信号量拥有的资源数为num
if (sem_init(&m_sem, 0, num) != 0)
{
throw std::exception();
}
}
// 释放资源,线程销毁之后调用这个函数即可
~sem()
{
sem_destroy(&m_sem);
}
// 调用该函数会将sem中的资源数-1。
// 若sem中的资源数>0,线程不会阻塞。
// 若sem中的资源数减为0,资源被耗尽,线程被阻塞。
bool wait()
{
return sem_wait(&m_sem) == 0;
}
// 调用该函数会将sem中的资源数+1。
// 若有线程调用wait()被阻塞,这时这些线程会解除阻塞,获取到资源之后继续向下运行。
bool post()
{
return sem_post(&m_sem) == 0;
}
private:
// 信号量
sem_t m_sem;
};
四、条件变量
- 条件变量的主要阻塞线程,不能完全保证线程安全,如果要保证线程安全,需要条件变量和互斥锁一起使用。
#include
#include
// 条件变量
class cond
{
public:
// 初始化条件变量
cond()
{
if (pthread_cond_init(&m_cond, NULL) != 0)
{
throw std::exception();
}
}
// 销毁释放资源
~cond()
{
pthread_cond_destroy(&m_cond);
}
// 线程阻塞函数,先把调用该函数的线程放入条件变量的请求队列。
// 如果线程已经对互斥锁上锁,那么会将这把锁打开,这样做是为了避免死锁。
// 在线程解除阻塞时,函数内部会帮助这个线程再次将锁锁上,继续向下访问临界区。
bool wait(pthread_mutex_t *m_mutex)
{
int ret = 0;
// 线程解除阻塞返回0 --> return true
ret = pthread_cond_wait(&m_cond, m_mutex);
return ret == 0;
}
// 将线程阻塞一定的时间长度,时间到达之后,线程就解除阻塞。
bool timewait(pthread_mutex_t *m_mutex, struct timespec t)
{
int ret = 0;
ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);
return ret == 0;
}
// 唤醒阻塞在条件变量上的线程,至少有一个被解除阻塞
bool signal()
{
return pthread_cond_signal(&m_cond) == 0;
}
// 唤醒阻塞在条件变量上的线程,被阻塞的线程全部解除阻塞
bool broadcast()
{
return pthread_cond_broadcast(&m_cond) == 0;
}
private:
// 条件变量
pthread_cond_t m_cond;
};
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)