Bootstrap

【TinyWebServer源码解析】(一)线程同步机制封装类

本文是对github上万star项目源码解析,供大家学习交流
项目地址:
https://github.com/qinguoyi/TinyWebServer

一、线程同步机制封装类(lock)

线程同步机制包装类

===============

多线程同步,确保任一时刻只能有一个线程能进入关键代码段.

​ =>信号量

​ =>互斥锁

​ =>条件变量

===============

共三个class,分别是sem、locker、cond

===============

#include <exception>
#include <pthread.h>
#include <semaphore.h>

包含以上头文件

  1. <exception>: 该头文件提供了异常处理机制所需的类和函数。其中最常用的类是 std::exception,它是所有 C++ 标准库异常的基类。
  2. <pthread.h>: 该头文件定义了 POSIX 线程(POSIX Threads,简称 pthreads)的 API。pthreads 是一种跨平台的线程库,可在许多不同的操作系统上使用。
  3. <semaphore.h>: 该头文件定义了信号量的 API。信号量是一种用于管理并发访问的同步原语。在多线程编程中,信号量通常用于保护共享资源,以避免竞争条件和死锁问题。
1、sem类(信号量)

sem类是一个信号量封装类。信号量是一种同步机制,用于控制多个线程之间的访问顺序。通常情况下,当多个线程需要共享一个资源时,需要使用信号量来确保每个线程按照一定的顺序访问该资源,从而避免出现竞争条件和数据不一致等问题

其中构建函数有两个sem()、sem(int num),他们的区别是是否给sem_init提供信号量初始值(value)

sem_init是一个用于初始化信号量的函数,它有三个参数,分别是:

  1. sem:指向要初始化的信号量的指针。
  2. pshared:指定信号量是在进程间共享还是在线程间共享的标志。如果pshared为0,则表示信号量将在同一进程内的线程之间共享;如果pshared为非0值,则表示信号量可以在多个进程之间共享。
  3. value:指定信号量的初始值。
class sem{
public:
    // 构造函数sem(),使用sem_init(&m_sem, 0, 0)构建
    sem(){
        if (sem_init(&m_sem, 0, 0) != 0){
            throw std::exception();
        }
    }
    // 构造函数sem(int num),并根据传入的参数设置初始值num,使用sem_init(&m_sem, 0, num)构建
    sem(int num){
        if (sem_init(&m_sem, 0, num) != 0){
            throw std::exception();
        }
    }
    // 销毁信号量对象,释放资源
    ~sem(){
        sem_destroy(&m_sem);
    }
    // 该方法会对信号量进行等待操作,如果信号量的值为0,则该方法会阻塞当前线程,直到有其他线程通过 post() 方法将信号量的值增加至大于0,才会唤醒该线程
    bool wait(){
        return sem_wait(&m_sem) == 0;
    }
    // 该方法会对信号量进行释放操作,将信号量的值增加1,唤醒可能被阻塞的线程
    bool post(){
        return sem_post(&m_sem) == 0;
    }

private:
    // 初始化一个名为 m_sem 的信号量的
    sem_t m_sem;
};
2、locker类(互斥锁)

这个类是一个简单的互斥锁封装,用于在多线程编程中保护共享资源。它使用 POSIX 线程库提供的 pthread_mutex_t 类型和相关函数来实现。

在多线程程序中,使用互斥锁可以避免多个线程同时访问共享资源而导致的数据竞争问题。这里的 lock() 和 unlock() 方法就是为了控制对共享资源的访问,只有获得了互斥锁的线程才能访问共享资源,其他线程则必须等待锁被释放才能获取它。

class locker{
public:
    // 构造函数:创建一个互斥锁并初始化。如果初始化失败,则抛出std::exception异常。
    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 库的函数
    pthread_mutex_t *get(){
        return &m_mutex;
    }

private:
    // 定义一个线程互斥锁,用来保护共享资源,防止多个线程同时进行读写操作而导致数据不一致的问题
    pthread_mutex_t m_mutex;
};
3、cond类(条件变量)

这个类使用 POSIX 线程库提供的条件变量来实现线程同步。

在使用条件变量时需要与互斥锁搭配使用,以避免竞态条件的发生。因此,wait 函数和 timewait 函数的参数中传入了一个互斥锁指针,表示等待前需要先加锁,等待结束后再解锁。同时,在每个成员函数中都有注释掉的 mutex 加锁和解锁操作,这是因为加锁和解锁的逻辑应该放在调用 cond 类的外部实现,而不应该在 cond 类的内部实现中。

class cond{
public:
    // 构造函数:调用 pthread_cond_init 函数对条件变量进行初始化。如果初始化失败,会抛出一个 std::exception 异常
    cond(){
        if (pthread_cond_init(&m_cond, NULL) != 0){
            //pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }
    // 析构函数:调用 pthread_cond_destroy 函数对条件变量进行销毁
    ~cond(){
        pthread_cond_destroy(&m_cond);
    }
    // wait 函数会一直等待,直到被其他线程唤醒
    bool wait(pthread_mutex_t *m_mutex){
        int ret = 0;
        //pthread_mutex_lock(&m_mutex);
        ret = pthread_cond_wait(&m_cond, m_mutex);
        //pthread_mutex_unlock(&m_mutex);
        return ret == 0;
    }
    // timewait 函数会在指定的时间内等待,如果时间到了仍然没有被唤醒,就会返回 false
    bool timewait(pthread_mutex_t *m_mutex, struct timespec t){
        int ret = 0;
        //pthread_mutex_lock(&m_mutex);
        ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);
        //pthread_mutex_unlock(&m_mutex);
        return ret == 0;
    }
    // signal 函数用于唤醒一个正在等待条件变量的线程
    bool signal(){
        return pthread_cond_signal(&m_cond) == 0;
    }
    // broadcast 函数用于唤醒所有正在等待条件变量的线程
    bool broadcast(){
        return pthread_cond_broadcast(&m_cond) == 0;
    }

private:
    //static pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
};

;