锁的封装和RAII实现

news/2024/11/14 20:56:03/

RAII(Resource Acquisition Is Initialization)是一种 C++ 中的编程技术,它利用了对象的生命周期和析构函数的特性来管理资源的获取和释放。在 RAII 中,资源的获取和释放都与对象的生命周期相关联,资源在对象构造时被获取,而在对象析构时被释放,从而确保资源的正确管理,避免资源泄漏和内存泄漏等问题。

RAII 的基本原则是:资源的获取应该在对象的构造函数中进行,而资源的释放应该在对象的析构函数中进行。这样,只要对象存在,资源就会得到正确的管理,无需手动管理资源的获取和释放。RAII 还可以结合异常安全性和自动化资源管理的优势,使得代码更加健壮和可靠。本文章中对下面的线程同步工具进行了封装,并通过RAII方便管理资源。

下面是对线程同步工具的说明和封装:

  • 互斥锁(Mutex)

    • 互斥锁用于保护临界区,确保同一时刻只有一个线程可以访问临界区的资源,其他线程必须等待锁被释放才能继续执行。
    • 互斥锁提供了两个主要的操作:锁定(lock)和解锁(unlock)。只有持有锁的线程才能访问被保护的资源,其他线程必须等待当前线程释放锁。
    • 互斥锁通常使用操作系统提供的原语来实现,因此在资源竞争较为频繁的情况下,可能会引入较高的开销。
class Mutex : public Uncopyable {
public:Mutex() {if(pthread_mutex_init(&mutex_, nullptr)) {throw std::runtime_error("pthread_mutex_init error");}}~Mutex() {pthread_mutex_destroy(&mutex_);}void lock() {if(pthread_mutex_lock(&mutex_)) {throw std::runtime_error("pthread_mutex_lock error");}}void unlock() {if(pthread_mutex_unlock(&mutex_)) {throw std::runtime_error("pthread_mutex_unlock error");}}pthread_mutex_t& getMutex() { return mutex_; }
private:pthread_mutex_t mutex_;
};
  • 信号量(Semaphore)

    • 信号量是一个计数器,用于控制对共享资源的访问。它允许多个线程同时访问共享资源,但是需要控制同时访问的线程数量。
    • 信号量可以分为二进制信号量和计数信号量。二进制信号量的计数器只有0和1两个值,用于实现互斥锁的功能;而计数信号量的计数器可以是任意正整数,用于控制同时访问资源的线程数量。
    • 信号量提供了等待(wait)和释放(post)两个主要的操作。等待操作会使计数器减一,如果计数器为负,则线程被阻塞;释放操作会使计数器加一,唤醒等待的线程。
    • 信号量通常用于进程间通信和线程同步的场景,是一种比互斥锁更加灵活的同步机制。
class Semaphore : public Uncopyable {
public: Semaphore(u_int32_t count = 0) {if(sem_init(&semaphore_, 0, count)) {throw std::runtime_error("sem_init error");}}~Semaphore() {sem_destroy(&semaphore_);}void wait() {if(sem_wait(&semaphore_)) {throw std::runtime_error("sem_wait error");}}void post() {if(sem_post(&semaphore_)) {throw std::runtime_error("sem_post error");}}bool trywait() {return (sem_trywait(&semaphore_)== 0);}bool timewait(const std::chrono::milliseconds& timeout) {struct timespec ts;clock_gettime(CLOCK_REALTIME, &ts);ts.tv_sec += timeout.count() / 1000;ts.tv_nsec += (timeout.count() % 1000) * 1000000;int result;do {result = sem_timedwait(&semaphore_, &ts);} while(result == -1 && errno == EINTR);if(result == -1) {if(errno == ETIMEDOUT) {return false;} else {throw std::runtime_error("sem_timeout error");}}return true;}
private:sem_t semaphore_;
};
  • 条件变量(Condition Variable)

    • 条件变量用于线程间的通信和同步,它允许线程在满足特定条件时进行等待,直到另一个线程唤醒它。
    • 条件变量通常与互斥锁一起使用,用于防止竞态条件(Race Condition)的发生。等待线程会先获取互斥锁,然后检查条件是否满足,如果条件不满足,则进入等待状态并释放锁;唤醒线程会在改变条件后通知等待的线程,并释放互斥锁。
    • 条件变量提供了等待(wait)、通知(notify)和广播(broadcast)等操作。等待操作会使线程进入等待状态,直到收到通知或广播;通知操作用于唤醒一个等待的线程;广播操作用于唤醒所有等待的线程。
class Cond : public Uncopyable {
public:Cond(Mutex& mutex):mutex_(mutex) {if(pthread_cond_init(&cond_, nullptr)) {throw std::runtime_error("pthread_cond_init error");}}   ~Cond() {pthread_cond_destroy(&cond_);}void wait() {mutex_.lock();if(pthread_cond_wait(&cond_, &mutex_.getMutex())) {mutex_.unlock();throw std::runtime_error("pthread_cond_wait error");}mutex_.unlock();}bool timedwait(const std::chrono::milliseconds& timeout) {mutex_.lock();struct timespec ts;clock_gettime(CLOCK_REALTIME, &ts);ts.tv_sec += timeout.count() / 1000;ts.tv_nsec += (timeout.count() % 1000) * 1000000;int result = pthread_cond_timedwait(&cond_, &mutex_.getMutex(),&ts);mutex_.unlock();if(result == ETIMEDOUT) {return false;} else if(result != 0) {throw std::runtime_error("pthread_cond_timewait error");} else {return true;}}void signal() {if(pthread_cond_signal(&cond_)) {throw std::runtime_error("pthread_cond_signal error");}}void broadcast() {if(pthread_cond_broadcast(&cond_)) {throw std::runtime_error("pthread_cond_broadcast error");}}
private:pthread_cond_t  cond_;Mutex& mutex_;
};
  • 自旋锁(Spin Lock)

    • 自旋锁是一种基于忙等待的锁,它不会使线程进入阻塞状态,而是在锁被释放之前一直尝试获取锁,这样会消耗 CPU 时间。
    • 自旋锁适用于临界区很小、锁的持有时间很短的情况,因为长时间的自旋可能会降低系统的性能。
    • 自旋锁通常使用原子操作来实现,因此在多核处理器上可以保证线程安全。
class SpinLock : public Uncopyable {
public:SpinLock() {if(pthread_spin_init(&spinlock_, 0)) {throw std::runtime_error("pthread_spinlock_init error");}}~SpinLock() {pthread_spin_destroy(&spinlock_);}void lock() {if(pthread_spin_lock(&spinlock_)) {throw std::runtime_error("pthread_spin_lock error");}}void unlock() {if(pthread_spin_unlock(&spinlock_)) {throw std::runtime_error("pthread_spin_unlock error");}}
private:pthread_spinlock_t spinlock_;
};
  • 原子锁(Atomic Lock)

    • 原子锁是一种特殊的自旋锁,它使用原子操作来实现临界区的访问,从而保证了线程安全性。
    • 原子锁通常只有两种状态:锁定和解锁,因此它比一般的自旋锁更加简单,但也更加高效。
    • 原子锁通常用于对共享资源进行简单的读写操作,例如增加或减少计数器的值等。
class AtomicLock : public Uncopyable {
public:AtomicLock() {atomic_.clear();}~AtomicLock() {// 不做处理}void lock() {atomic_.test_and_set(std::memory_order_acquire);}void unlock() {atomic_.clear(std::memory_order_release);}
private:std::atomic_flag atomic_;
};
  • 读写锁(Read-Write Lock)

    • 读写锁是一种特殊的锁机制,用于控制对共享资源的读取和写入操作。
    • 读取操作可以并发执行,多个线程可以同时获取读取锁并进行读取操作,但写入操作必须独占执行,一个线程获取写入锁后其他线程无法获取读取锁或写入锁。
    • 读写锁适用于读取操作频繁而写入操作较少的场景,可以提高并发读取性能。

 

class RWLock : public Uncopyable {
public:RWLock() {if(pthread_rwlock_init(&rwmutex_, nullptr)) {throw std::runtime_error("pthread_rwlock_init error");}}~RWLock() {pthread_rwlock_destroy(&rwmutex_);}void readLock() {if(pthread_rwlock_rdlock(&rwmutex_)) {throw std::runtime_error("pthread_rwlock_rdlock error");}}void writeLock() {if(pthread_rwlock_wrlock(&rwmutex_)) {throw std::runtime_error("pthread_rwlock_wrlock error");}}void unlock() {if(pthread_rwlock_unlock(&rwmutex_)) {throw std::runtime_error("pthread_rwlock_unlock error");}}
private:pthread_rwlock_t rwmutex_;
};

现在已经完成对锁的封装,下面实现RAII。考虑到除了读写锁外都只包含lock上锁函数和unlock解锁函数外,使用模板来实现RAII机制。

template<class LockType>
class LockGuard {
public:LockGuard(LockType& locktype):locktype_(locktype) {locktype_.lock();is_locked_ = true;}~LockGuard() {unlock();}void lock() {if(!is_locked_) {locktype_.lock();is_locked_ = true;}}void unlock() {if(is_locked_) {locktype_.unlock();is_locked_ = false;}}
private:LockType& locktype_;bool is_locked_ = false;
};

如上代码所示,is_locked变量是为了避免直接去尝试加锁或解锁。在构造函数中进行加锁,在析构函数中解锁。在使用时,需要在类中声明。假设要在Mutex中使用,如下所示:

class Mutex{

...

typedef LockGuard<Mutex> Lock;

...

};

// 调用

Mutex::Lock lock(mutex);

Mutex::Lock时typedef LockGuard<Mutex>,根据Mutex实例化模板,也就是用Mutex初始化出来一个新的类。lock是这个新类创建的对象,该对象在构造函数中完成加锁,在析构时解锁。

下面是对读写锁的封装,和上面类似,只是调用的加锁函数有所不同。

template <class LockType>
class ReadLockGuard {
public:ReadLockGuard(LockType& locktype):locktype_(locktype) {locktype_.lock();is_locked_ = true;}~ReadLockGuard() {unlock();}void lock() {if(!is_locked_) {locktype_.readLock();is_locked_ = true;}}void unlock() {if(is_locked_) {locktype_.unlock();is_locked_ = false;}}
private:   LockType& locktype_;bool is_locked_ = false;
};template <class LockType>
class WriteLockGuard {
public:WriteLockGuard(LockType& locktype):locktype_(locktype) {locktype_.lock();is_locked_ = true;}~WriteLockGuard() {unlock();}void lock() {if(!is_locked_) {locktype_.writeLock();is_locked_ = true;}}void unlock() {if(is_locked_) {locktype_.unlock();is_locked_ = false;}}
private:   LockType& locktype_;bool is_locked_ = false;
};

最后给出测试函数:

Semaphore sem(1);void printSemaphore(size_t i) {std::cout << "Thread " << i << " access sem" << std::endl;// sem.wait();int ret = sem.timewait(std::chrono::milliseconds(1000));std::cout << "ret = " << ret << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));sem.post();std::cout << "Thread " << i << " get sem" << std::endl;
}void testSemaphore() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printSemaphore, i));}for(auto& thread : threads) {thread.join();}
}Mutex mutex;void printMutex(size_t i) {std::cout << "Thread " << i << " access mutex" << std::endl;Mutex::Lock lock(mutex);std::cout << "Thread " << i << " get mutex" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Thread " << i << " release mutex" << std::endl;
}void testMutex() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printMutex, i));}for(auto& thread : threads) {thread.join();}
}void printSpinLock(size_t i) {std::cout << "Thread " << i << " access spinlock" << std::endl;// SpinLock::Lock lock();AtomicLock::Lock lock();std::cout << "Thread " << i << " get spinlock" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Thread " << i << " release spinlock" << std::endl;
}void testSpinLock() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printSpinLock, i));}for(auto& thread : threads) {thread.join();}
}Mutex mutex_cond;
Cond cond(mutex_cond);void printCond(size_t i) {std::cout << "Thread " << i << " begin" << std::endl;cond.wait();std::cout << "Thread " << i << " end" << std::endl;
}void testCond() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printCond, i));}std::this_thread::sleep_for(std::chrono::seconds(2));cond.broadcast();// for(int i = 0; i < 4; ++i) {//     std::this_thread::sleep_for(std::chrono::seconds(1));//     cond.signal();// }for(auto& thread : threads) {thread.join();}  
}void printAtomic(size_t i) {std::cout << "Thread " << i << " begin" << std::endl;AtomicLock::Lock lock();std::cout << "Thread " << i << " end" << std::endl;
}void testAtomic() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printAtomic, i));}std::this_thread::sleep_for(std::chrono::seconds(2));for(auto& thread : threads) {thread.join();}  
}void printRWLock(size_t i) {std::cout << "Thread " << i << " begin" << std::endl;// RWLock::ReadLock lock();RWLock::WriteLock lock();std::cout << "Thread " << i << " end" << std::endl;
}void testRWLock() {std::vector<std::thread> threads;threads.reserve(4);for(size_t i = 0; i < 4; ++i) {threads.emplace_back(std::bind(printRWLock, i));}std::this_thread::sleep_for(std::chrono::seconds(2));for(auto& thread : threads) {thread.join();}  
}int main() {// testSemaphore();// testMutex();// testSpinLock();// testCond();// testAtomic();testRWLock();return 0;
}

在测试时发现了条件变量的问题,在唤醒线程时需要保证线程已经跑起来了,因此测试函数中睡眠了2秒。此外互斥量和条件变量搭配使用,wait或者timewait需要已经上锁的互斥量,在wait或timedwait后释放互斥量。


http://www.ppmy.cn/news/1436686.html

相关文章

【学习AI-相关路程-自我总结-相关入门-自我学习-NVIDIA-Jetson】

【学习AI-相关路程-自我总结-相关入门-自我学习】 1、前言2、思考前进方向3、学习路线1、基础知识阶段2、初级准备阶段3、中级学习阶段4、高级实战阶段 4、自我的努力5、学习平台6、自己总结 1、前言 最近AI相关比较火的&#xff0c;对于程序员&#xff0c;或者走这行的人来说…

FPV眼镜和VR眼镜的区别,穿越机搭配FPV眼镜优缺点分析

FPV眼镜&#xff0c;即第一人称视角&#xff08;First Person View&#xff09;眼镜&#xff0c;是专为无人机、穿越机、遥控模型等飞行设备设计的头戴式显示器。这种设备能够将飞行设备上的摄像头所捕捉的实时图像传输到眼镜中&#xff0c;让佩戴者仿佛亲自驾驶飞行器一样&…

【docker 】docker-compose 部署mongoDB

在notepad中将格式改为UNIX &#xff08;编辑》文档格式转化》转为Unix&#xff09;&#xff0c;编码改为UTF-8 &#xff08; 编码》转为UTF-8&#xff09;&#xff0c;改好后如图 新建启动脚本 mongo.sh #!/bin/bash # 挂载路径 DATA_DIR/opt/docker-data/mongodb/data LOG_…

R-Tree的选择策略与分裂算法

文章目录 选择策略最小面积增长&#xff08;Minimal Area Enlargement, MAE&#xff09;**最小周长增长&#xff08;Minimal Perimeter Enlargement, MPE&#xff09;**全局最小覆盖&#xff08;Global Minimum Bounding Box, GMBB&#xff09; 分裂算法最均匀二分&#xff08;…

java-Spring-bean的生命周期

定义 程序中的每个对象都有生命周期&#xff0c;对象的创建、初始化、应用、销毁的整个过程称之为对象的生命周期&#xff1b; 在对象创建以后需要初始化&#xff0c;应用完成以后需要销毁时执行的一些方法&#xff0c;可以称之为是生命周期方法&#xff1b; 在spring中&…

springSecurity简单直接说明

引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombo…

基于机器学习的节日大促营销模型

基于机器学习来构建节日大促的营销模型&#xff0c;分为几个步骤&#xff1a; 1. 需求定义 跟业务确定需要建模的目标&#xff08;预计圈选会员数&#xff09;&#xff0c;预计圈选时间以此确定模型交付时间&#xff0c;今年大促的活动周期&#xff08;方便根据同个周期选取去…

服务网关GateWay基础

1. 网关基础介绍1.1 网关是什么1.2 为啥要用网关1.3 常见的网关组件NginxNetflix ZuulSpring Cloud GatewayKongAPISIX综合比较 2. gateWay的使用2.1 springCloud整合gateway2.2 GateWay的相关用法2.3 GateWay路由使用示例基本用法转发/重定向负载请求动态路由 2.5 断言(Predic…