Bootstrap

C++实现Web服务器项目笔记01

C++实现Web服务器项目笔记01

说明

用作学习目的。原项目地址: github链接

01 线程同步机制包装类

本部分为locker.h中的代码,该部分实现了多线程同步所需要的锁、信号量以及条件变量,以确保任一时刻只能有一个线程能进入临界区

信号量class sem

class sem
{
public:
    sem()
    {
        if (sem_init(&m_sem, 0, 0) != 0)
        {
            throw std::exception();
        }
    }
    sem(int num)
    {
        if (sem_init(&m_sem, 0, num) != 0)
        {
            throw std::exception();
        }
    }
    ~sem()
    {
        sem_destroy(&m_sem);
    }
    bool wait()
    {
        return sem_wait(&m_sem) == 0;
    }
    bool post()
    {
        return sem_post(&m_sem) == 0;
    }

private:
    sem_t m_sem;
};

该部分定义一个sem_t类型的私有属性作为信号量。

sem_init()

用于初始化信号量的函数。它是 POSIX 信号量库中的函数,用于创建和初始化一个信号量。函数原型如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • sem:指向要初始化的信号量的指针。
  • pshared:指定信号量的共享属性。如果为 0,表示信号量只能在当前进程的线程间共享;如果为非零值,表示信号量可以在多个进程间共享。
  • value:指定信号量的初始值。它表示可以同时进入临界区的线程数量,或者可以执行某个操作的资源数量。

函数的返回值表示函数执行的结果,它可以用于判断是否成功初始化信号量。返回值的含义如下:

  • 如果 sem_init() 成功初始化信号量,则返回值为 0。
  • 如果 sem_init() 初始化信号量失败,则返回值为 -1,并且会设置相应的错误码,可以通过 errno 来获取具体的错误信息。
sem_wait()

用于等待信号量的函数。它是 POSIX 信号量库中的函数,用于实现线程间的同步和互斥操作。

函数原型如下:

int sem_wait(sem_t *sem);

参数说明:

  • sem:指向要等待的信号量的指针。

函数的工作流程如下:

  1. 线程调用 sem_wait() 时,它会尝试获取信号量。
  2. 如果信号量的值大于 0,表示有可用的资源,调用线程可以继续执行,并将信号量的值减少 1。
  3. 如果信号量的值为 0,表示没有可用的资源,调用线程将被阻塞,进入等待状态,直到有其他线程调用 sem_post() 来增加信号量的值。
  4. 当有其他线程调用 sem_post() 来释放资源时,被阻塞的线程中的一个线程将被唤醒,并继续执行后续代码。

通过这种方式,sem_wait() 函数实现了线程的同步和互斥操作。它允许线程等待可用的资源,并在资源可用时继续执行。需要注意的是,在使用 sem_wait() 时,需要确保有其他线程会增加信号量的值,以避免死锁情况。

sem_wait() 函数的返回值表示函数执行的结果,它可以用于判断等待信号量的操作是否成功。返回值的含义如下:

  • 如果 sem_wait() 成功等待信号量,并成功减少信号量的值,则返回值为 0。特别地,当信号量的值为 0 时,调用 sem_wait() 后的返回值并不会被立即设置,此时,调用 sem_wait() 的线程会被阻塞,函数将一直等待直到成功获取到信号量后才会返回。
  • 如果 sem_wait() 等待信号量失败,则返回值为 -1,并且会设置相应的错误码,可以通过 errno 来获取具体的错误信息。
sem_post()

用于释放信号量的函数。它是 POSIX 信号量库中的函数,用于实现线程间的同步和互斥操作。

函数原型如下:

int sem_post(sem_t *sem);

参数说明:

  • sem:指向要释放的信号量的指针。

函数的工作流程如下:

  1. 当线程调用 sem_post() 时,它会增加信号量的值。
  2. 如果有其他线程正在等待该信号量,其中的一个线程将被唤醒,并继续执行后续代码。
  3. 如果没有其他线程在等待该信号量,信号量的值将增加,以便后续调用 sem_wait() 的线程能够成功获取到信号量并继续执行。

需要注意的是,sem_post() 只是增加信号量的值,并唤醒等待该信号量的线程(如果有的话)。

返回值:

  • 如果 sem_post() 成功释放信号量并增加信号量的值,则返回值为 0。
  • 如果 sem_post() 函数失败,则返回值为 -1,并且会设置相应的错误码,可以通过 errno 来获取具体的错误信息。
sem_destory()

用于销毁信号量的函数。它是 POSIX 信号量库中的函数,用于释放与信号量相关的资源并将信号量设置为无效状态。

函数原型如下:

int sem_destroy(sem_t *sem);

参数说明:

  • sem:指向要销毁的信号量的指针。

函数的工作流程如下:

  1. 当线程调用 sem_destroy() 时,它会销毁指定的信号量。
  2. 销毁信号量会释放与信号量相关的资源,并将信号量设置为无效状态。
  3. 销毁后的信号量不能再被使用,需要重新初始化才能使用。

通过 sem_destroy() 函数,可以在不再需要使用信号量时,释放与之相关的资源,防止资源泄漏。

需要注意的是,销毁信号量时,必须确保没有其他线程正在使用该信号量,否则会导致未定义的行为。

返回值:

  • 如果 sem_destroy() 成功销毁信号量,则返回值为 0。
  • 如果 sem_destroy() 函数失败,则返回值为 -1,并且会设置相应的错误码,可以通过 errno 来获取具体的错误信息。

互斥锁locker

互斥锁的目的是确保在任何给定的时间点只有一个线程能够执行临界区代码,从而避免多个线程同时访问共享资源而导致的数据竞争和不一致性。

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;
};

该部分定义一个 pthread_mutex_t类型的私有属性作为互斥锁。

pthread_mutex_init()

用于初始化互斥锁(mutex),为互斥锁对象分配内存并设置初始属性。该函数的原型如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • mutex:指向互斥锁对象的指针,需要在调用函数之前分配内存。
  • attr:指向互斥锁属性对象的指针,用于设置互斥锁的属性。如果传递 NULL,则使用默认属性。

pthread_mutex_init() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示互斥锁初始化成功。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_mutex_lock()

用于加锁互斥锁(mutex),它会阻塞当前线程,直到成功获取到互斥锁为止。该函数的原型如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • mutex:指向互斥锁对象的指针。

pthread_mutex_lock() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示当前线程成功获取了互斥锁。

  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h>

pthread_mutex_unlock()

用于解锁互斥锁(mutex),将互斥锁从被锁定的状态释放出来,使其他线程能够获取该互斥锁。该函数的原型如下:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • mutex:指向互斥锁对象的指针。

pthread_mutex_unlock() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示互斥锁成功解锁。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_mutex_destroy()

用于销毁互斥锁(mutex)对象,并释放与之相关的资源。该函数的原型如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • mutex:指向互斥锁对象的指针。

pthread_mutex_destroy() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示互斥锁成功销毁。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。

条件变量(cond

用于线程之间的同步和通信。它允许一个线程在满足特定条件之前等待,而其他线程可以通过发送信号来通知等待的线程条件已经满足。

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;
        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;
};
pthread_cond_init()

用于初始化条件变量(cond),为条件变量对象分配内存并设置初始属性。函数原型如下:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • cond:指向条件变量对象的指针,需要在调用函数之前分配内存。
  • attr:指向条件变量属性对象的指针,用于设置条件变量的属性。如果传递 NULL,则使用默认属性。

pthread_cond_init() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示条件变量初始化成功。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_cond_destroy()

用于销毁条件变量(cond)对象,并释放与之相关的资源。函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);
  • cond:指向条件变量对象的指针。

pthread_cond_destroy() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示条件变量成功销毁。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_cond_wait()

用于等待条件变量(cond)满足特定条件,如果条件不满足,则阻塞当前线程。函数原型如下:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • cond:指向条件变量对象的指针。
  • mutex:指向互斥锁对象的指针。在等待条件变量之前,需要先获取互斥锁。(等待条件变量之前,线程需要获取互斥锁,这样可以确保在等待条件变量时,其他线程无法修改条件变量的状态。如果在等待条件变量之前不获取互斥锁,那么当多个线程同时等待条件变量时,可能会导致竞态条件的发生,即多个线程同时修改条件变量的状态,从而导致不确定的行为。)

pthread_cond_wait() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示线程成功等待条件变量。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_cond_timedwait()

类似于 pthread_cond_wait(),但是在指定的时间内等待条件变量满足特定条件,如果超过指定时间仍未满足条件,则返回。函数原型如下:

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
  • cond:指向条件变量对象的指针。
  • mutex:指向互斥锁对象的指针。在等待条件变量之前,需要先获取互斥锁。
  • abstime:指向 timespec 结构的指针,表示等待的绝对时间。

pthread_cond_timedwait() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示线程成功等待条件变量。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。(如果超过指定时间仍未满足条件,该函数会返回一个特定的错误码:ETIMEDOUT。)
pthread_cond_signal()

用于发送信号给等待条件变量的线程,通知其中的一个线程条件已经满足。函数原型如下:

int pthread_cond_signal(pthread_cond_t *cond);
  • cond:指向条件变量对象的指针。

pthread_cond_signal() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示成功发送信号给等待的线程。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。
pthread_cond_broadcast()

用于发送广播信号给等待该条件变量的所有线程,通知它们条件已经满足。函数原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);
  • cond:指向条件变量对象的指针。

pthread_cond_broadcast() 函数返回一个整数值,表示函数执行的结果。以下是可能的返回值:

  • 成功情况下,返回值为 0,表示成功发送广播信号给等待的线程。
  • 如果返回值是一个正数,表示函数执行失败,具体的错误代码可以参考 <errno.h> 头文件中定义的错误常量。

小结

这段代码定义了三个同步机制的封装类:semlockercond,分别对应信号量、互斥锁和条件变量。

  • sem类封装了信号量操作,通过sem_initsem_waitsem_post函数实现信号量的初始化、等待和释放操作。

  • locker类封装了互斥锁操作,通过pthread_mutex_initpthread_mutex_lockpthread_mutex_unlock函数实现互斥锁的初始化、加锁和解锁操作。

  • cond类封装了条件变量操作,通过pthread_cond_initpthread_cond_waitpthread_cond_timedwaitpthread_cond_signalpthread_cond_broadcast函数实现条件变量的初始化、等待、定时等待、信号和广播操作。

这些封装类提供了简单且易用的接口,方便在多线程程序中进行同步和通信操作。通过使用这些封装类,可以避免直接使用底层的线程库函数,提高代码的可读性和可维护性。

;