目录
1.基础知识
(1)RALL
(2)信号量
(3)互斥量
(4)条件变量
2.功能
1.基础知识
(1)RALL
RALL全称“Resource Acquisition is Initialization”,翻译过来就是“资源获取即初始化”。用于管理资源的生命周期。RALL的核心思想是将资源的获取与对象的初始化绑定在一起,资源的释放与对象的析构绑定在一起。通过这种方式,确保资源在使用完毕后被正确释放,避免资源泄漏。
RALL的工作原理
1.资源获取即初始化——在对象的构造函数中获取资源(如内存、文件句柄、锁等)。
2.资源释放即析构——在对象的析构函数中释放资源。
由于C++的机制,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
RALL的优点
1.避免资源泄漏:资源在析构时自动释放,无需手动管理。
2.异常安全:即使在代码中发生异常,资源也会被正确释放。
3.代码简洁:不需要显式释放资源,减少代码的复杂性。
RALL的典型应用
1.智能指针。2.文件句柄管理。3.锁管理。
(2)信号量
信号量是一种用于控制多个线程或进程对共享资源访问的同步机制。主要用于解决并发编程中的同步问题。信号量的核心是一个整数计数器,用于表示可用资源的数量。通过两个原子操作(wait和signal)来管理资源的分配和释放。
信号量的基本操作
1.wait(或p操作)
如果信号量的值大于0,则将其减1,表示资源被占用。
如果信号量的值为0,则线程或进程会被阻塞,直到信号量的值大于0。
2.signal(或v操作)
将信号量的值加1,表示资源被释放。
如果有线程或进程正在等待该信号量,则唤醒其中一个。
信号量的类型
1.二进制信号量。信号量的值只能是0或1。类似于互斥锁,用于实现互斥访问。
2.计数信号量:信号量的值可以是任意非负整数。用于控制对多个资源的访问。
信号量的应用场景
1.资源池管理:例如数据库连接池、线程池等。信号量可以限制同时使用的资源数量。
2.生成者-消费者模型:信号量可以用于同步生产者和消费者线程,确保缓冲区不会溢出或下溢。
3.读写锁:信号量可以实现读写锁,允许多个读线程同时访问资源,但只允许一个写线程访问。
注意事项
1.死锁:信号量使用不当可能会导致死锁。
2.性能开销:信号量的实现依赖操作系统内核,频繁调用会带来额外的开销。
(3)互斥量
互斥量是一种用于多线程编程的同步机制,用于保护共享资源,防止多个线程同时访问或修改这些资源。从而避免数据竞争和不一致问题,
互斥量的核心思想是:当一个线程持有互斥量时,其他线程必须等待,直到该线程释放互斥量后才能继续访问共享资源。
互斥量的基本操作
1.加锁:线程尝试获取互斥量的所有权。如果互斥量已被其他线程持有,则当前线程会被阻塞,直到互斥量被释放。
2.解锁:线程释放互斥量的所有权。其他等待的线程可以尝试获取互斥量。
互斥量的特性
1.互斥性:同一时刻只能有一个线程可以持有互斥量。
2.所有权:只有加锁的线程才能解锁互斥量。
3.阻塞机制:如果互斥量被其他线程持有,尝试加锁的线程会被阻塞,直到互斥量被释放。
互斥量的应用场景
1.保护共享数据。2.实现线程安全的单例模式。3.同步多个线程的操作。
互斥量的注意事项
1.死锁:如果多个线程互相等待对方释放锁,可能会导致死锁。
避免死锁的方法包括:按固定顺序加锁,使用lock一次性加锁多个互斥量。
2.性能开销:互斥量的加锁和解锁操作会带来额外的性能开销,尤其在高并发场景下。
3.锁粒度:锁的粒度应尽可能小,以减少线程阻塞的时间。
(4)条件变量
条件变量是一种用于多线程编程的同步机制,通常与互斥量结合使用。用于实现线程间的等待与通知机制。条件变量的核心作用是允许线程在某些条件不满足时进入等待状态,并在条件满足时被唤醒。
条件变量的基本操作
1.wait:线程调用wait时会释放互斥量,并进入等待状态。当其他线程调用notify_one或notify_all时,等待的线程会被唤醒,重新获得互斥量。
2.notify_one:唤醒一个正在等待的线程。
3.notify_all:唤醒所有正在等待的线程。
典型应用场景
1.生产者-消费者模型:当缓冲区为空时,消费者线程等待,当缓冲区有数据时,生产者线程通知消费者线程。当缓存区满时,生产者线程等待,当缓冲区有空闲时,通知生产者。
2.任务队列:当任务队列为空时,工作线程等待,当任务队列不为空时,通知工作线程。
3.事件驱动编程:线程等待某个事件发生,事件发生后通知等待的线程。
注意事项
1.虚假唤醒:即没有调用notify_one或notify_all,等待的线程也可能被唤醒。可以在wait中使用条件判断(Lambda表达式)检查条件是否真正满足。
2.死锁:条件变量使用不当,可能会导致死锁。
3.性能开销:条件变量的操作涉及系统调用,可能会带来额外的性能开销。
2.功能
封装的功能
类中主要对Linux下的三种锁进行封装,将锁的创建与销毁函数封装在类的构造和析构函数中,实现RALL机制。例如
class sem{
public:
//构造函数
sem()
{
//信号量初始化
if(sem_init(&m_sem,0,0)!=0){
throw std::exception();
}
}
//析构函数
~sem()
{
//信号量销毁
sem_destroy(&m_sem);
}
private:
sem_t m_sem;
};
将重复的代码封装成函数,减少重复。
1 //条件变量的使用机制需要配合锁来使用
2 //内部会有一次加锁和解锁
3 //封装起来会使得更加简洁
4 bool wait()
5 {
6 int ret=0;
7 pthread_mutex_lock(&m_mutex);
8 ret=pthread_cond_wait(&m_cond,&m_mutex);
9 pthread_mutex_unlock(&m_mutex);
10 return ret==0;
11 }
12 bool signal()
13 {
14 return pthread_cond_signal(&m_cond)==0;
15 }