什么是线程安全?确保线程安全的方法!生产者-消费者模型!

什么是线程安全?

线程安全就是在多执行流中,对临界资源进行争抢访问,不会造成数据的二义性或者逻辑异常

临界资源:多线程执行流共享的资源就是临界资源。
临界区:每个线程内部,访问临界资源的代码。
互斥:任何时刻,保证只有一个执行流访问临界资源。
原子性:不会被任何调度机制打断,该操作要么完成,要么未完成。

确保线程安全的方法

通过同步与互斥方式实现线程安全。

同步:通过条件判断实现对资源获取的合理性。

互斥:通过同一时间只有一个执行流能够访问资源,实现资源访问的安全性。

互斥

互斥锁是互斥方式的实现方式。

什么是互斥锁

互斥锁就是只有0/1的计数器,描述临界资源的可访问/不可访问的状态。一个执行流在访问期间需要把资源状态标记为不可访问,结束后标记为可访问。

这些访问同一资源的执行流在访问前,先去访问互斥锁,判断访问状态。

操作流程

  1. 定义互斥锁变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

  1. 初始化互斥锁变量

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

  1. 在访问临界资源前加锁

pthread_mutex_lock(pthread_mutex_t *mutex);
如果不能加锁则阻塞等待。

pthread_mutex_trylock(pthread_mutex_t *mutex);
如果不能加锁则报错返回。–EBUSY。

  1. 对临界资源访问完毕后进行解锁。

pthread_mutex_unlock(pthread_mutex_t *mutex);

  1. 如果不使用互斥锁了则释放资源

thread_mutex_destroy(pthread_mutex_t *mutex);

注意

  • 互斥锁只能保护资源访问的安全性,不能保证访问的合理性。
  • 所有线程访问同一临界资源,必须访问同一个互斥锁进行保护。

死锁

多个执行流因为资源争夺,但是因为推进顺序不当,陷入互相等待,导致程序流程无法继续推进。

另一篇文章讲解死锁:https://blog.csdn.net/lyly_h/article/details/108281483

同步

通过条件判断,决定执行流是否能够获取资源,保证资源获取的合理性

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了。

例如:

当一个线程访问队列时,发现队列为空,它只能等待,只能等待其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。

提供了一个pcb等待队列,并且提供了使线程休眠已经唤醒的接口

执行过程

  1. 定义条件变量

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

  1. 初始化条件变量

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

  1. 在执行流不满足获取资源条件的时候调用接口执行流阻塞等待

thread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

条件变量搭配互斥锁一起使用。

  1. 在执行流满足获取资源条件之后唤醒等待执行流。

pthread_cond_broadcast(pthread_cond_t *cond);
(至少唤醒一个)
pthread_cond_signal(pthread_cond_t *cond);
(唤醒所有等待的执行流)

  1. 不使用条件变量则释放资源

pthread_cond_destroy(pthread_cond_t *cond)

生产者消费者模型

通过一个容器来解决生产者和消费者之间强耦合的问题。

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生
产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的

321原则:一个场所、两种角色、三种关系。

应对场景:数据的产生与处理场景。

优点:

  • 解耦合:降低各模块程序之间的强关联或依赖性。
  • 支持并发:支持对线程生产以及多线程处理提高效率。
  • 支持忙闲不均:产生快、处理慢有可能造成数据丢失,队列可以起到数据缓冲的作用。

采用C++代码模拟实现:

#include<iostream>
#include<queue>
#include<stdlib.h>
#include<pthread.h>

#define NUM 8

class BlockQueue
{
private:
	std::queue<int> q;//线程队列容器
	int cap;//队列中最大节点数量
	pthread_mutex_t lock;//互斥锁
	pthread_cond_t full;//消费者等待队列
	pthread_cond_t empty;//生产者等待队列
private:
	void LockQueue()
	{
		pthread_mutex_lock(&lock);
	}
	void UnLockQueue()
	{
		pthread_mutex_unlock(&lock);
	}
	void ProductWait()
	{
		pthread_cond_wait(&full, &lock);
	}
	void ConsumeWait()
	{
		pthread_cond_wait(&empty, &lock);
	}
	void NotifyProduce()
	{
		pthread_cond_signal(&full);
	}
	void NotifyConsume()
	{
		pthread_cond_signal(&empty);
	}
	bool IsEmpty()
	{
		return(q.size() == 0 ? true : false);
	}
	bool IsFull()
	{
		return(q.size() == cap ? true : false);
	}

public:
	BlockQueue(int _cap = NUM) :cap(_cap)
	{//构造函数 初始化
		pthread_mutex_init(&lock, NULL);
		pthread_cond_init(&full, NULL);
		pthread_cond_init(&empty, NULL);
	}

	void PushData(const int& data)
	{//线程安全的数据入队操作
		LockQueue();
		while (IsFull())
		{
			NotifyConsume();
			std::cout << "queue full, notify consume data, product stop" << std::endl;
			ProductWait();
		}
		q.push(data);
		UnLockQueue();
	}

	void PopData(const int& data)
	{//出队
		LockQueue();
		while (IsEmpty())
		{
			NotifyProduct();
			std::cout << "queue empty, notify product data, consume stop" << std::endl;
			ConsumrWait();
		}
		data = q.front();
		q.pop();
		UnLockQueue();
	}

	~BlockQueue()
	{//析构函数 释放资源
		pthread_mutex_destroy(&lock);
		pthread_cond_destroy(&empty);
		pthread_cond_destroy(&full);
	}
};

void *consumer(void *arg)
{
	BlockQueue *bpq = (BlockQueue*)arg;
	int data;
	for ( ; ; )
	{
		bpq->PopData(data);
		std::cout << "consume data done:" << data << std::endl;
	}
}

void *producer(void *arg)
{
	BlockQueue *bpq = (BlockQueue*)arg;
	srand((unsigned long)time(NULL));
	for (;;)
	{
		int data = rand() % 1024;
		bpq->PushData(data);
		std::cout << "product data done:" << data << std::endl;
	}
}

int main()
{
	BlockQueue bp;
	pthread_t c, p;
	pthread_create(&c, NULL, consumer, (void*)&bp);
	pthread_create(&p, NULL, producer, (void*)&bp);

	pthread_join(c, NULL);
	pthread_join(p, NULL);
	return 0;
}

信号量

本质是一个计数器,和一个PCB等待队列。是用来实现线程(进程)间的同步与互斥。

信号量实现互斥:保证最高计数为1,表示同一时间只有一个线程能够访问资源,访问前计数+1,访问完毕后-1。

信号量实现同步:通过对资源的计数,判断资源有多少,访问是否合理。(有资源才能访问,没有资源则阻塞进程)。

实现步骤与接口:

  1. 定义信号量

sem_t;

  1. 初始化信号量

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

sem:定义的信号量
pshared:0–表示线程间共享,非0–表示进程间共享
val:信号量初始值

  1. 访问资源前先访问信号量,判断是否合理,不合理则阻塞,合理计数-1返回。

int sem_wait(sem_t *sem);

  1. 产生一个资源后,对计数+1,并且唤醒等待队列中等待的线程

int sem_post(sem_t *sem);

  1. 销毁一个信号量

int sem_destroy(sem_t *sem);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值