基础知识
RAII
- RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”. 是一个用于管理资源(如内存、文件句柄、网络连接等)的编程范式
- 在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定
- RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现资源和状态的安全管理,智能指针是RAII最好的例子
#include <iostream>
#include <memory> // 包含智能指针的头文件class Resource {
public:Resource() { std::cout << "Resource acquired.\n"; }~Resource() { std::cout << "Resource released.\n"; }
};void useResource() {std::unique_ptr<Resource> res = std::make_unique<Resource>(); // 创建并自动管理Resource对象// 这里可以使用res指向的资源// ...
} // res自动销毁,它指向的Resource对象也随之被删除int main() {useResource();// 资源已经被释放,无需担心内存泄漏std::cout << "Back in main.\n";return 0;
}
信号量
信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(P)和信号(V).假设有信号量SV,对其的P、V操作如下:
- P,如果SV的值大于0,则将其减一;若SV的值为0,则挂起执行
- V,如果有其他进行因为等待SV而挂起,则唤醒;若没有,则将SV值加一
信号量的取值可以是任何自然数,最常用的,最简单的信号量是二进制信号量,只有0和1两个值.
- sem_init函数用于初始化一个未命名的信号量
- sem_destory函数用于销毁信号量
- sem_wait函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞
- sem_post函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程
以上,成功返回0,失败返回errno
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>sem_t sem; // 信号量void* producer(void* arg) {// 生产数据printf("Producing data...\n");sleep(2); // 模拟数据生产需要的时间printf("Data ready.\n");// 发送信号sem_post(&sem);return NULL;
}void* consumer(void* arg) {// 等待信号量sem_wait(&sem);// 处理数据printf("Consuming data...\n");return NULL;
}int main() {pthread_t tid1, tid2;// 初始化信号量,初始值为0sem_init(&sem, 0, 0);// 创建生产者和消费者线程pthread_create(&tid1, NULL, producer, NULL);pthread_create(&tid2, NULL, consumer, NULL);// 等待线程结束pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 销毁信号量sem_destroy(&sem);return 0;
}
互斥量(锁)
互斥锁,也成互斥量,可以保护关键代码段,以确保独占式访问.当进入关键代码段,获得互斥锁将其加锁;离开关键代码段,唤醒等待该互斥锁的线程.
- pthread_mutex_init函数用于初始化互斥锁
- pthread_mutex_destory函数用于销毁互斥锁
- pthread_mutex_lock函数以原子操作方式给互斥锁加锁
- pthread_mutex_unlock函数以原子操作方式给互斥锁解锁
以上,成功返回0,失败返回errno
#include <iostream>
#include <pthread.h>
#include <semaphore.h>pthread_mutex_t mutex;
sem_t sem;void* setData(void* arg) {// 加锁pthread_mutex_lock(&mutex);std::cout << "Setting data...\n";sleep(1); // 模拟数据设置需要的时间std::cout << "Data set.\n";// 解锁pthread_mutex_unlock(&mutex);// 释放信号量,通知数据已准备好sem_post(&sem);return nullptr;
}void* processData(void* arg) {// 等待信号量,确保数据已经设置sem_wait(&sem);// 加锁pthread_mutex_lock(&mutex);std::cout << "Processing data...\n";sleep(1); // 模拟数据处理需要的时间std::cout << "Data processed.\n";// 解锁pthread_mutex_unlock(&mutex);return nullptr;
}int main() {pthread_t t1, t2;// 初始化互斥锁和信号量pthread_mutex_init(&mutex, nullptr);sem_init(&sem, 0, 0);// 创建线程pthread_create(&t1, nullptr, setData, nullptr);pthread_create(&t2, nullptr, processData, nullptr);// 等待线程结束pthread_join(t1, nullptr);pthread_join(t2, nullptr);// 销毁互斥锁和信号量pthread_mutex_destroy(&mutex);sem_destroy(&sem);return 0;
}
条件变量
条件变量提供了一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程.
- pthread_cond_init函数用于初始化条件变量
- pthread_cond_destory函数销毁条件变量
- pthread_cond_broadcast函数以广播的方式唤醒所有等待目标条件变量的线程
- pthread_cond_wait函数用于等待目标条件变量.该函数调用时需要传入 mutex参数(加锁的互斥锁) ,函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上. 也就是说函数内部会有一次解锁和加锁操作.
使用条件变量,结合互斥锁,来协调两个线程的操作,确保第一个线程(生产者)准备数据后,第二个线程(消费者)才开始处理数据。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>// 全局共享数据
int ready = 0;// 互斥锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;// 生产者函数
void* producer(void* arg) {// 获取互斥锁pthread_mutex_lock(&mutex);printf("Producer is preparing data...\n");sleep(2); // 模拟数据准备时间ready = 1; // 标记数据已准备好printf("Producer has prepared data.\n");// 通知等待的线程(消费者),数据已准备好pthread_cond_broadcast(&cond);// 释放互斥锁pthread_mutex_unlock(&mutex);return NULL;
}// 消费者函数
void* consumer(void* arg) {// 获取互斥锁pthread_mutex_lock(&mutex);// 当数据还未准备好时,等待条件变量while (ready == 0) {printf("Consumer is waiting for data...\n");pthread_cond_wait(&cond, &mutex); // 等待条件变量,同时释放互斥锁}// 数据准备好后,继续执行printf("Consumer is processing data.\n");// 释放互斥锁pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t t1, t2;// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);// 创建生产者和消费者线程pthread_create(&t1, NULL, producer, NULL);pthread_create(&t2, NULL, consumer, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
在条件变量被触发之前,线程不会执行,这有助于节省CPU资源,避免无效的轮询。
功能
锁机制的功能
- 实现多线程同步,通过锁机制,确保任一时刻只能有一个线程能进入关键代码段.
封装的功能
- 类中主要是Linux下三种锁进行封装,将锁的创建与销毁函数封装在类的构造与析构函数中,实现RAII机制
#ifndef LOCKER_H
#define LOCKER_H#include <semaphore.h>
#include <exception>
#include <pthread.h>// 信号量类,用于线程间的同步
class sem {
public:// 默认构造函数,初始化一个初始值为0的二进制信号量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);}// 信号量等待操作,如果成功返回 truebool wait() {// 尝试获取信号量,如果成功则返回 true,否则返回 falsereturn sem_wait(&m_sem) == 0;}// 信号量发布操作,如果成功返回 truebool post() {// 发布信号量,如果成功则返回 true,否则返回 falsereturn sem_post(&m_sem) == 0;}
private:sem_t m_sem; // 内部信号量对象
};// 互斥锁类,用于线程同步
class locker {
public:// 构造函数:初始化互斥锁locker() {// 初始化互斥锁,如果初始化失败,则抛出异常if (pthread_mutex_init(&m_mutex, nullptr) != 0) {throw std::exception();}}// 析构函数:销毁互斥锁~locker() {// 销毁互斥锁pthread_mutex_destroy(&m_mutex);}// 尝试获取互斥锁bool lock() {// 如果成功获取互斥锁,则返回truereturn pthread_mutex_lock(&m_mutex) == 0;}// 尝试释放互斥锁bool unlock() {// 如果成功释放互斥锁,则返回true// 注意:这里错误地使用了pthread_mutex_destroy,应该是pthread_mutex_unlockreturn 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, nullptr) != 0) {throw std::exception();}}// 析构函数,销毁条件变量~cond() {// 销毁条件变量pthread_cond_destroy(&m_cond);}/*** @brief 等待条件变量* * @param m_mutex 用于锁定的互斥锁,确保等待时的互斥访问* @return true 等待成功* @return false 等待失败*/bool wait(pthread_mutex_t* m_mutex) {int ret = 0;// 等待条件变量,释放互斥锁并等待通知ret = pthread_cond_wait(&m_cond, m_mutex);return ret == 0;}/*** @brief 在限定时间内等待条件变量* * @param m_mutex 用于锁定的互斥锁,确保等待时的互斥访问* @param t 超时时间点* @return true 等待成功* @return false 等待失败或超时*/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;}/*** @brief 发送信号唤醒等待条件变量的一个线程* * @return true 信号发送成功* @return false 信号发送失败*/bool signal() {// 唤醒等待条件变量的一个线程return pthread_cond_signal(&m_cond) == 0;}/*** @brief 广播唤醒所有等待条件变量的线程* * @return true 广播成功* @return false 广播失败*/bool broadcast() {// 唤醒所有等待条件变量的线程return pthread_cond_broadcast(&m_cond) == 0;}private:pthread_cond_t m_cond; // 条件变量
};#endif