day01:lock – 锁
代码:
locker.h文件
#ifndef LOCKER_H
#define LOCKER_H
#include<semaphore.h> //信号量头文件
#include<pthread.h> //互斥量、条件变量头文件
#include<exception> //初始化锁异常
class sem{
public:
//构造函数1:默认新创建的的信号量初始值为0
sem(){
if(sem_init(&m_sem,0,0) != 0){
throw std::exception();
}
}
//构造函数2:默认新创建的信号量初始值为 num
sem(int num){
if(sem_init(&m_sem,0,num) != 0){
throw std::exception();
}
}
//析构函数:销毁信号量
~sem(){
if(sem_destroy(&m_sem) != 0){
throw std::exception();
}
}
//信号量的变化
//wait() -- 信号量减1
bool wait(){
return sem_wait(&m_sem) == 0;
}
//post() -- 信号量增加1
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(){
if(pthread_mutex_destroy(&m_mutex) != 0){
throw std::exception();
}
}
//关锁 -- 进入临界区
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(){
if (pthread_cond_destroy(&m_cond) != 0) {
throw std::exception();
}
}
/** 阻塞等待:
* 1. 阻塞等待条件变量cond(参1)满足
* 2. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
* [两步为一个原子操作]
* 3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
*/
bool wait(pthread_mutex_t* m_mutex){ //无限时间阻塞
int ret = 0;
ret = pthread_cond_wait(&m_cond,m_mutex);
return ret == 0;
}
bool timedwait(pthread_mutex_t* m_mutex,const 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
总结:
- 线程同步机制包装类:多线程同步,确保任一时刻只能有一个线程能进入关键代码段.
信号量
初始化函数
- int sem_init(sem_t *sem, int pshared, unsigned int value)
- 成功返回0,失败返回其他值
- 参数1:信号量的地址变量
参数2:是否和其他进程共享该信号量? 0 不,1 是
参数3:信号量的初始值
销毁函数
- int sem_destroy(sem_t *sem)
- 成功返回0,失败返回其他值
- 参数1:信号量的地址变量
wait函数 – 信号量减1
- int wait(sem_t *sem);
- 成功返回0,失败返回其他值
- 参数1:信号量的地址变量
post函数 – 信号量加1
- int post(sem_t *sem);
- 成功返回0,失败返回其他值
- 参数1:信号量的地址变量
互斥锁
初始化函数
- int pthread_mutex_init(pthread_mutex_t mutex, const_pthread_mutexattr_tattr)
- 成功返回0,失败返回其他值
- 参数1:互斥信号量的地址变量
参数2:指定互斥量的属性,如果没有特殊指定,填NULL
销毁函数
- int pthread_mutex_destroy(pthread_mutex_t *mutex)
- 成功返回0,失败返回其他值
- 参数1:互斥信号量的地址变量
加锁
- return pthread_mutex_lock(pthread_mutex_t *mutex);
- 成功返回0,失败返回其他值
- 参数1:互斥信号量的地址变量
解锁
- return pthread_mutex_unlock(pthread_mutex_t *mutex);
- 成功返回0,失败返回其他值
- 参数1:互斥信号量的地址变量
条件变量
原文链接:https://blog.csdn.net/qq_39736982/article/details/82380689
条件变量概述:
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待
主要应用函数:
-
pthread_cond_init()函数 功能:初始化一个条件变量
-
pthread_cond_wait()函数 功能:阻塞等待一个条件变量
-
pthread_cond_timedwait()函数 功能:限时等待一个条件变量
-
pthread_cond_signal()函数 功能:唤醒至少一个阻塞在条件变量上的线程
-
pthread_cond_broadcast()函数 功能:唤醒全部阻塞在条件变量上的线程
-
pthread_cond_destroy()函数 功能:销毁一个条件变量
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号
函数分析
参数类型:
-
pthread_cond_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:
-
pthread_cond_t cond; 变量cond只有两种取值1、0。
初始化一个条件变量
-
int pthread_cond_init(pthread_cond_t *restrict_cond, const pthread_condattr_t * restrict_attr);
-
参2:attr表条件变量属性,通常为默认值,传NULL即可
-
也可以使用静态初始化的方法,初始化条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
阻塞等待一个条件变量
-
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
-
函数作用:
- 阻塞等待条件变量cond(参1)满足
- 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
- [1.2.两步为一个原子操作]
- 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
限时等待一个条件变量
-
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict_mutex, const struct timespec *restrict_abstime);
-
参3: 参看man sem_timedwait函数,查看struct timespec结构体。
struct timespec { time_t tv_sec; /* seconds */ 秒 long tv_nsec; /* nanosecondes*/ 纳秒 }
-
形参abstime:绝对时间。
-
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
-
struct timespec t = {1, 0};
-
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)
-
唤醒至少一个阻塞在条件变量上的线程
- int pthread_cond_signal(pthread_cond_t *cond);
唤醒全部阻塞在条件变量上的线程
- int pthread_cond_broadcast(pthread_cond_t *cond);
销毁一个条件变量
- int pthread_cond_destroy(pthread_cond_t *cond);
问题
Q1. 为什么需要 获取 互斥锁 地址变量的函数?
- 这是因为,条件变量通常和互斥锁一起使用
- 封装这个函数,是为了可以直接获取当前互斥锁的地址变量,将其作为条件变量中阻塞函数的形参。
Q2. 为什么需要三种锁,他们是如何配合使用的?
-
互斥锁和信号量都是用于多线程编程中的同步原语。它们之间的主要区别在于它们的用途和行为。
-
互斥锁用于保护共享资源,确保同一时间只有一个线程访问该资源。当一个线程获得互斥锁时,其他试图获得该锁的线程将被阻塞,直到锁被释放。互斥锁的加锁和解锁必须由同一线程分别对应使用。
-
信号量则用于控制对共享资源的访问,它可以允许多个线程同时访问共享资源。信号量的值可以为非负整数,表示可用资源的数量。当一个线程申请一个资源时,信号量的值减1;当一个线程释放一个资源时,信号量的值加1。当信号量的值为0时,试图申请资源的线程将被阻塞,直到有其他线程释放资源。信号量可以由一个线程释放,另一个线程得到。
-
总之,互斥锁用于保护共享资源,确保同一时间只有一个线程访问该资源;而信号量用于控制对共享资源的访问,可以允许多个线程同时访问共享资源。
-
而强调一点,条件变量不是一种锁,更确切地说,它应该是一种阻塞机制,只是配上mutex后,也可以起到保护共享资源的效果。