TinyWebServer项目笔记——01 线程同步机制封装类

发布于:2025-03-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

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   }