线程安全(信号量)


前面我们已经说过,多线程之间自己不具备同步与互斥的功能,所以在多线程访问临界资源过程中,需要去限制线程,使之是安全的,前面我们提到了互斥的实现–互斥锁,同步的实现–条件变量,今天我们接着谈一个 信号量

一、什么是信号量

信号量本质是一个计数器和一个队列pcb等待队列,通过其实现进程间或者线程间的同步与互斥,关于信号量的同步与互斥的实现,我在博客 进程间通信(下) 中谈到过,虽然当时讲到的是多进程,不过与多线程类似,大家可以自行体会,在这里就不多说了。

二、相关接口

2.1 定义信号量

sem_t

2.2 初始化信号量

接口:int sem_init(sem_t * sem, int pshared, unsigned int value);

在这里插入图片描述
返回值:成功返回0,失败返回-1。

2.3 P操作

在这里插入图片描述
接口1:int sem_wait(sem_t * sem);
功能:对应信号量计数大于0时,可以访问并且计数-1,计数小于等于0时,不能访问,并且线程陷入等待。
接口2:int sem_trywait(sem_t * sem);
功能:对应信号量计数大于0时,可以访问并且计数-1,计数小于等于0时,不能访问,并且线程报错返回。
接口3:int sem_timedwait(sem_t * sem, const struct timespec * abs_timeout);
功能:在指定时间内,对应信号量计数大于0时,可以访问并且计数-1,计数小于等于0时,不能访问,并且线程陷入等待,超过时间,还是不能访问,则报错返回。
返回值:成功返回0,失败返回-1。
错误原因:需要通过errno确定
常见的几个错误原因:
EINTR(针对所有等 待接口)–表示阻塞被信号打断。
ETIMEDOUT(只针对sem_timedwait接口)–表示sem_timedwait接口等到超时。
EAGAIN(只针对sem_trywait接口)–表示非阻塞情况下,计数为0。

2.4 V操作

接口:int sem_post(sem_t * sem);
在这里插入图片描述
功能:计数+1,如果当前有等待的进程,则唤醒一个等待的进程。
返回值:成功返回0,失败返回-1。

2.5 信号量的销毁

接口:int sem_destroy(sem_t * sem);
在这里插入图片描述
功能:销毁一个信号量。
返回值:成功返回0,失败返回-1。

三、信号量的应用–环形队列

上面我们谈到了什么是信号量,以及信号量的相关接口,下面我们就应用它的本质以及它的相关接口去实现一个环形队列,来保障线程安全

3.1环形队列的属性

class RingQueue{
  private:
    vector<int> _list;  //数组,相当于队列
    int _cap;        //队列容量
    int _read;       //用于消费者读的下标
    int _write;     //用于生产者写的下标
    sem_t _lock;  //用于实现互斥
    //用于实现同步
    sem_t _pro;   //生产者,用于空闲计数
    sem_t _con;   //消费者,用于数据计数
};

这个环形队列类似于我们C++中的循环队列,但是他还包括实现线程间互斥和同步的相关信号量。

3.2 初始化

 //初始化
    RingQueue(int cap)
      :_list(cap)
       ,_cap(cap)
       ,_read(0)
       ,_write(0)
  {
    sem_init(&_lock,0,1);  //用于互斥,所以资源为1   
    sem_init(&_pro,0,cap);     
    sem_init(&_con,0,0);
  }

初始化需要徐将队列的容量给出,并且读写的其起始位置都是0,这时对于生产者(对空闲计数)而言,可用容量给定计数大小,对于消费者(对资源进行计数)而言,当前没有数据,所以计数为0,而通过给定一个信号量的计数为1来实现互斥。

3.3 出队与入队

   //入队
    bool push(const int data){
      sem_wait(&_pro);
      sem_wait(&_lock);
      _list[_write]=data;
      _write=(_write+1)%_cap;
      sem_post(&_lock);
      sem_post(&_con);
      return true;
    }
    //出队
    bool pop(int *buf){
      sem_wait(&_con);
      sem_wait(&_lock);
      *buf=_list[_read];
      _read=(_read+1)%_cap;
      sem_post(&_lock);
      sem_post(&_pro);
      return true;
    }

这里只需要注意,在数据生产或者处理过程中需要加锁处理,这里就通过信号量的计数最大为1,来实现互斥,并且是环形队列,所以需要去进行循环处理,也就是每次下标移动需要去模上容量,放置越界,而当数据时,消费之阻塞等待,当数据满了时,生产者阻塞等待,这里的阻塞等待也是通过对应的信号量通过计数实现的,并且在每次处理完一个数据后,需要去唤醒一个等待中的生产者,每次生产完一个数据后,需要去唤醒一个等待中的消费者,实现数据的轮询生产处理。

3.4 销毁信号量

    ~RingQueue(){
      sem_destroy(&_lock);
      sem_destroy(&_pro);
      sem_destroy(&_con);
    }

这里没有什么多说的,就是销毁用户自己定义的信号量。

3.5 环形队列实现具体代码

#include<iostream>
#include<vector>
#include<pthread.h>
#include <semaphore.h>
using namespace std;
class RingQueue{
private:
	vector<int> _list;  //数组,相当于队列
	int _cap;        //队列容量
	int _read;       //用于消费者读的下标
	int _write;     //用于生产者写的下标
	sem_t _lock;  //用于实现互斥
	//用于实现同步
	sem_t _pro;   //生产者,用于空闲计数
	sem_t _con;   //消费者,用于数据计数
public:
	//初始化
	RingQueue(int cap)
		:_list(cap)
		, _cap(cap)
		, _read(0)
		, _write(0)
	{
		sem_init(&_lock, 0, 1);  //用于互斥,所以资源为1   
		sem_init(&_pro, 0, cap);
		sem_init(&_con, 0, 0);
	}
	//入队
	bool push(const int data){
		sem_wait(&_pro);
		sem_wait(&_lock);
		_list[_write] = data;
		_write = (_write + 1) % _cap;
		sem_post(&_lock);
		sem_post(&_con);
		return true;
	}
	//出队
	bool pop(int *buf){
		sem_wait(&_con);
		sem_wait(&_lock);
		*buf = _list[_read];
		_read = (_read + 1) % _cap;
		sem_post(&_lock);
		sem_post(&_pro);
		return true;
	}
	~RingQueue(){
		sem_destroy(&_lock);
		sem_destroy(&_pro);
		sem_destroy(&_con);
	}

};
void *cus_fun(void* arg){
	RingQueue *Queue = (RingQueue*)arg;
	while (1){
		int data;
		Queue->pop(&data);
		printf("%p thread put data:%d\n", pthread_self(), data);

	}
}
void *pro_fun(void *arg){
	RingQueue* Queue = (RingQueue*)arg;
	int i = 0;
	while (1){
		Queue->push(i);
		printf("%p thread get data:%d\n", pthread_self(), i++);
	}
}
int main()
{
	pthread_t ctid[3], ptid[3];
	int ret;
	RingQueue Queue(5);
	for (int i = 0; i < 3; i++){
		ret = pthread_create(&ctid[i], NULL, cus_fun, (void*)&Queue);
		if (ret != 0){
			printf("create cus error\n");
			return -1;
		}
	}
	for (int i = 0; i < 3; i++){
		ret = pthread_create(&ptid[i], NULL, pro_fun, (void*)&Queue);
		if (ret != 0){
			printf("create pro error\n");
			return -1;
		}
	}
	for (int i = 0; i < 3; i++){
		pthread_join(ctid[i], NULL);
	}
	for (int i = 0; i < 3; i++){
		pthread_join(ptid[i], NULL);
	}
	return 0;
}

通过两个线程组,使用同一个环形队列,进行同步与互斥处理,实现了生产者与消费者模型。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redisson信号量可以用来阻塞线程。当信号量的许可数量为0时,尝试获取许可的线程会被阻塞,直到其他线程释放了许可。通过控制许可数量,可以限制同时访问某个资源的线程数量,以保证资源的合理使用。 具体来说,Redisson提供了tryAcquire()方法来尝试获取信号量的许可。当许可数量为0时,该方法会阻塞线程,直到有可用的许可为止。 通过使用Redisson信号量,可以实现线程之间的同步和资源的有序访问,确保线程的安全性和资源的合理利用。这在多线程编程中非常有用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [day109-缓存-分布式锁-Redisson-信号量测试](https://blog.csdn.net/JavaCoder_juejue/article/details/115057538)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [python线程信号量semaphore使用解析](https://download.csdn.net/download/weixin_38526979/12857460)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [redisson中Semaphore的信号量介绍及其原理](https://blog.csdn.net/qq_50652600/article/details/131576796)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值