服务器编程:互斥锁,信号量,条件变量

引言
我们在写一个服务器项目时,很大概率会应用到线程池和数据库连接池等技术,但是在使用这些技术时我们肯定会遇到多线程竞争资源的问题还有剩余资源的数量问题,当剩余资源为空时,我们就不能再申请资源,所以我们需要用互斥锁,信号量,条件变量等工具进行一个约束.

文章以下提供的函数都是POSIX 线程库中的函数
可以在linux操作系统上运行,而不能在windows操作系统上运行.

目录

  • 互斥锁
  • 信号量
  • 条件变量
  • 源代码

互斥锁

用于保护关键代码段,保证同一时间只能有一个线程访问该代码段
当线程进入该代码段后,会申请互斥锁,然后其他申请不到互斥锁的线程会被阻塞,当该线程退出代码段之后,释放互斥锁,并唤醒之前阻塞的线程

使用互斥锁必须的头文件

#include 

pthread_mutex_t表示一个互斥锁类型
互斥锁的相关函数
1.pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

初始化一个互斥锁对象,可以通过attr传递属性,如果不需要额外属性,则设置为NULL即可

2.pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex);

用于销毁一个互斥锁

3.pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

用于互斥锁加锁

4.pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

用于互斥锁的解锁

PS:再次提醒以下,这些函数都是不能在Windosw操作系统上运行的,如果想在Windows操作系统上实现这样的功能,需要使用Windows线程库上的相关函数.

如果想跨操作系统编程,涉及到互斥锁的内容,可以使用跨平台的线程库
如:Boost.Thread和C++11提供的标准库< mutex> ,

信号量

信号量是特殊的变量,只有取自然数值并只有两种操作,等待(P),信号(V),假如有变量SV
P操作:让sv-1,当sv=0时,执行挂起操作
V操作: 唤醒因为SV被挂起的线程,如果没有,sv+1

使用信号量必须的头文件:

#include 

sem_t表示信号量类型
信号量相关函数:
1.sem_init

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

用于初始化一个信号量
sem表示要初始化的信号量指针
pshared表示信号量的共享方式,如果为0,表示进程内线程间共享,如果为1,表示可在进程之间共享
value表示信号量的初始值

2.sem_destroy

int sem_destroy(sem_t *sem)

用于销毁一个信号量对象

3.sem_wait

int sem_wait(sem_t *sem)

将信号量的值-1,如果信号量值为0,阻塞调用的线程

4.sem_post

int sem_post(sem_t *sem)

将信号量的值+1,并唤醒因为信号量阻塞的线程

5.sem_trywait

int sem_trywait(sem_t *sem)

尝试将信号量的值-1,如果尝试失败,则返回失败.

条件变量

让线程之间共享一些数据,当共享数据达到某个值时,执行相应的操作

使用条件变量的头文件

#include  

pthread_cond_t表示一个条件变量类型
条件变量相关的函数

1.pthread_cond_init

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

用于初始化一个条件变量
你可以通过 attr 参数传递属性对象,如果不需要额外的属性,可以将其设置为 NULL。

  1. pthread_cond_destroy
int pthread_cond_destroy(pthread_cond_t *cond);

销毁一个条件变量

  1. pthread_cond_wait
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

用于等待条件变量
在调用之前,必须先获得mutex互斥锁,函数会自动释放锁,并在等待期间将线程阻塞。当条件变量发出信号时,线程会被唤醒,并重新获得 mutex 的锁。

为什么要先获取互斥锁:
这是因为条件变量的等待和唤醒操作必须在互斥锁的保护下进行,以确保线程安全和正确性。

pthread_cond_wait 函数被设计为在等待条件变量时自动释放互斥锁,以允许其他线程获取该互斥锁并对共享资源进行操作。
但是,在释放互斥锁之前,需要确保线程在等待条件变量之前获得了互斥锁,这样可以避免竞态条件和数据不一致的问题。
如果在等待条件变量之前不使用互斥锁进行保护,多个线程可能会同时进入等待状态,从而导致竞态条件和不确定的行为。

通过先获得互斥锁,可以确保在一个时间点只有一个线程在等待条件变量,并且其他线程会在获得互斥锁后等待或继续执行。

4.pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);

用于给一个线程发送条件变量信号,唤醒一个阻塞的线程

5.pthread_cond_broadcast

int pthread_cond_broadcast(pthread_cond_t *cond);

用于给全部等待条件变量的线程发送信号,唤醒所有阻塞的线程

6.pthread_cond_timedwait

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

允许线程在指定时间内等待条件变量的信号

mutex:指向互斥锁的指针,调用前需要先获取锁。
abstime:指向 timespec 结构体的指针,表示等待的绝对时间点

成功等待到条件变量的信号:返回值为 0。
超时:返回值为 ETIMEDOUT,表示等待超时。
出现错误:返回值为其他非零错误码,表示发生了具体的错误。

源代码

locker.h 取自Tinywebserver项目

#ifndef LOCKER_H
#define LOCKER_H

#include //抛出异常的一个库!
#include  //多线程编程的库!
#include //信号量的库!

class sem
{
public:
    sem()
    {
        //初始化一个信号量,初始值为0
        if (sem_init(&m_sem, 0, 0) != 0)
        {
            throw std::exception();
        }
    }
    sem(int num)
    {
        //创建一个信号量,初始值为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;
};

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; //用来表示互斥锁的数据类型!
};


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;
        //用于等待条件变量!,但是在调用之前必须先获得mutex锁!
        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;
};
#endif

你可能感兴趣的:(服务器,c++)