从零开始的 C++ web服务器

本文章用作记录个人第一个c++ web服务器的实现过程。其中内容参考游双著《Linux高性能服务器编程》与微信公众号“两猿社”的整合教程。

day1 线程同步机制封装类

RAII

RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”.

在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定

RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现资源和状态的安全管理,智能指针是RAII最好的例子

本模块功能

锁机制的功能

实现多线程同步,通过锁机制,确保任一时刻只能有一个线程能进入关键代码段.

封装的功能

类中主要是Linux下三种锁进行封装,将锁的创建于销毁函数封装在类的构造与析构函数中,实现RAII机制

模块详解

信号量

信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(P)和信号(V).假设有信号量SV,对其的P、V操作如下:

P,如果SV的值大于0,则将其减一;若SV的值为0,则挂起执行

V,如果有其他进行因为等待SV而挂起,则唤醒;若没有,则将SV值加一

信号量的取值可以是任何自然数,最常用的,最简单的信号量是二进制信号量,只有0和1两个值.

相关函数:

// 头文件
#include <semaphore.h>

// 初始化
sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在这个进程的所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。如果 pshared 是非零值,那么信号量将在进程之间共享,并且应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。因为通过 fork(2) 创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。所有可以访问共享内存区域的进程都可以用sem_post(3)、sem_wait(3) 等等操作信号量。初始化一个已经初始的信号量其结果未定义。

int sem_init(sem_t *sem,int pshared,unsigned int value); 
// 销毁
int sem_destroy(sem_t *sem); 

// sem_wait函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞
int sem_wait(sem_t *sem);  // 资源减少1
int sem_trywait(sem_t *sem);  
// sem_post函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程
int sem_post(sem_t *sem);  // 资源增加1
int sem_getvalue(sem_t *sem); 

以上,成功返回0,失败返回errno

互斥量

互斥锁,也成互斥量,可以保护关键代码段,以确保独占式访问.当进入关键代码段,获得互斥锁将其加锁;离开关键代码段,唤醒等待该互斥锁的线程.

//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a)

//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *m)

//以原子操作方式给互斥锁加锁
int pthread_mutex_lock(pthread_mutex_t *m)

//以原子操作方式给互斥锁解锁
int pthread_mutex_lock(pthread_mutex_t *m)

以上,成功返回0,失败返回errno

条件变量

条件变量提供了一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程.

pthread_cond_init函数用于初始化条件变量

pthread_cond_destory函数销毁条件变量

pthread_cond_broadcast函数以广播的方式唤醒所有等待目标条件变量的线程

pthread_cond_wait函数用于等待目标条件变量.该函数调用时需要传入 mutex参数(加锁的互斥锁) ,函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上. 也就是说函数内部会有一次解锁和加锁操作.

代码

#ifndef LOCKER_H
#define LOCKER_H

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

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

class locker
{
private:
    pthread_mutex_t m_mutex;
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;
    }
};

class cond{
private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
public:
    cond(){
        if(pthread_mutex_init(&m_mutex,NULL)!=0){
            throw std::exception();
        }
        if(pthread_cond_init(&m_cond,NULL)!=0){
            pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }
    ~cond(){
        pthread_mutex_destroy(&m_mutex);
        pthread_cond_destroy(&m_cond);
    }
    bool wait(){
        int ret=0;
        pthread_mutex_lock(&m_mutex);
        ret=pthread_cond_wait(&m_cond,&m_mutex);
        pthread_mutex_unlock(&m_mutex);
        return ret==0;
    }
};

#endif

发表评论

电子邮件地址不会被公开。 必填项已用*标注