【Linux】线程安全

线程安全

	**线程之间对临界资源的安全访问**
			因为在cpu足够的情况下,多个线程的运行可能是并行的,一次对邻接资源的访问,就可能
			造成争抢操作,会造成数据的二义性问题;
			因此线程安全就是讨论如何保证线程对临界资源的安全访问;
			使用同步与互斥解决!!
			同步:	对临界资源的访问的可控性;
			互斥:	对临界资源同时间的唯一访问
	互斥 :互斥锁   ----必须是原子操作不可被打断
			一个只有0/1的计数器
			1-有资源能操作
			0-没资源则(阻塞/报错返回)
			加锁时,计数器不为0 则 减1;为0则阻塞/报错返回
			解锁时,计数置1
			通过互斥锁来保证临界资源的安全访问;
			互斥锁也是一个临界资源,因此互斥锁本身就必须是原子操作。
	死锁:线程/进程因为一直获取不到锁资源,导致阻塞卡死,一般讨论同时出现多个锁造成死锁的情况。
			死锁产生的四个必要条件:
					1.互斥条件		---唯一时间只能一人操作
					2.不可剥脱条件	---谁的锁只能解
					3.请求与保持		---拿第一个取第二个,拿不到第二个,但也不放手第一个
					4.环路等待条件
			避免死锁:破坏必要条件
			避免死锁:银行家算法,死锁检测算法
			[银行家算法](https://www.cnblogs.com/chuxiuhong/p/6103928.html)
		(加载-》处理-》换回)
	同步 :条件变量
			pthread_cond_wait
					集合解锁,休眠,加锁的原子操作
					因为加锁是非阻塞的,所有条件判断应该是循环的
			不同角色的线程应该等待在不同的条件变量上,防止错误唤醒导致卡死
			多个商人和多个消费者的时候---消费者消费结束应该唤醒商人,但是实际情况唤醒的不一定是卖面的,因为此时会出问题,所以需要分开等待分开唤醒

在这里插入图片描述

生产者与消费者模型:生产者与消费者

生产者抓取数据,将数据放到缓冲区,消费者从缓冲区拿到数据并处理
支持忙闲不均;	解耦和;	支持并发;
缓冲区需要考虑线程安全问题!
一个场所,两类角色,三种关系
生产者与生产者:互斥
消费者与消费者:互斥
生产者与消费者:同步+互斥
c++中 std::queue 不是线程安全的(解决线程安全问题需要耗费更多时间)

信号量:POSIX标准信号量

具有等待队列的计数器(统计资源数,判断现在是否有资源)
实现进程/线程间的同步与互斥(不同于互斥锁的只要01两种计数)
互斥锁的计数 + 条件变量里的等待和唤醒 ~= 信号量
POSIX标准信号量:
	计数器是一个资源计数
	获取资源
		计数大于0,表示有资源可供操作,计数-1
		计数不大于0,表示没有资源可供操作,陷入休眠
	释放资源
		计数+1,并且唤醒等待在队列上的pcb
	sem_init
	sem_destroy
	sem_wait :			计数不大于0,表示没有资源可供操作,陷入休眠
	sem_timewait:		限定时间内计数不大于0,没有资源可供操作,陷入休眠
	sem_post:			唤醒等待队列上的pcb
	使用c++中的vector实现线程安全的环形队列
		sem.cpp	
/*  基于C++中的vector容器实现线程安全的环形队列
 *  vector---一个线性表(数组)
 * */


#include <iostream>
#include <vector>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

class RingQueue
{
    private:
	std::vector<int> _list;
	int _cap;
	pthread_mutex_t _mutex;
	sem_t _space;
	sem_t _data;
	int _pro_step;
	int _con_step;
    public:
	RingQueue(int cap = 10):_cap(cap),_list(cap),_pro_step(0),_con_step(0)
	{
	    sem_init(&_space,0,cap);
	    sem_init(&_data,0,0);
	    pthread_mutex_init(&_mutex,NULL);
	}
	~RingQueue()
	{
	    sem_destroy(&_space);
	    sem_destroy(&_data);
	}
	bool QueuePush(int data)
	{
	    sem_wait(&_space);
	    pthread_mutex_lock(&_mutex);
	    _list[_pro_step] = data;
	    _pro_step++;
	    _pro_step %= _cap;
	    pthread_mutex_unlock(&_mutex);
	    sem_post(&_data);
	    return true;
	}
	bool QueuePop(int *data)
	{
	    sem_wait(&_data);
	    pthread_mutex_lock(&_mutex);
	    *data = _list[_con_step];
	    _con_step++;
	    _con_step %= _cap;
	    pthread_mutex_unlock(&_mutex);
	    sem_post(&_space);
	    return true;
	}
};

void* thr_con(void *arg)
{
    RingQueue *q = (RingQueue*)arg;
    while(1)
    {
	usleep(1000);
	int data;
	q->QueuePop(&data);
	printf("consumer get data:%d\n",data);
    }
}
void* thr_pro(void *arg)
{
    RingQueue *q = (RingQueue*)arg;
    int i = 0;
    while(1)
    {
	printf("productor put data:%d\n",i);
	q->QueuePush(i++);
    }
}
int main()
{
    pthread_t ctid[4],ptid[4];
    int i , ret;
    RingQueue q;
    for(i = 0; i < 4; i++)
    {
	ret = pthread_create(&ctid[i],NULL,thr_con,(void*)&q);
	if(ret != 0)
	{
	    printf("create thread  error\n");
	    return -1;
	}
	ret = pthread_create(&ptid[i],NULL,thr_pro,(void*)&q);
	if(ret != 0)
	{
	    printf("create thread error\n");
	    return -1;
	}
    }
    for(i = 0; i < 4; i++)
    {
	pthread _join(ctid[i],NULL);
	pthread_join(ptid[i],NULL);
    }
}

读写锁:

  写互斥,读共享 		
  	 write	写的时候判断写计数和读计数,若都为0则可以写操作,否则 阻塞 		
  	 read	读的时候只需要判断写计数,若为0,则可以读,否则 阻塞
  读写锁使用自旋锁实现的?
  自旋锁循环对资源进行判断cpu消耗大---自旋锁的使用场景
  场景: 操作时间短,并且读操作多,写操作少
  读写锁默认是读优先的,因此当读者多写者少时,设置默认为写优先

线程池 一个或多个线程 + 任务队列

启动线程任务请求,
1.若同一时间因为大量请求而创建大量线程,有可能导致资源耗尽,程序崩溃(需要限制上限)
2.t1(创建线程) + t2(处理任务) + t3(销毁任务) = t
若t1 + t3 / t 比例较大,这意味着创建/销毁线程占据了大量时间,对于程序效率来说,是不可取的。
功能:
1.避免峰值压力,导致资源耗尽
2.避免大量的线程创建/销毁成本
实现:

#include<iostream>
#include<queue>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<time.h>

class MyTask
{
    private:
	int _data;
    public:
	MyTask(int data):_data(data){}
	void SetData(int data){_data = data;}
	void Run()
	{   
	    srand(time(NULL));
	    int s = rand() % 5;
	    printf("thread %p get data %d sleep -- %d\n",pthread_self(),_data,s);
	    sleep(s);
	}
};

class ThreadPool
{
    private:
	std::queue<MyTask *> _list;
	int _cap;
	pthread_mutex_t _mutex;
	pthread_cond_t _full;
	pthread_cond_t _empty;

	int _max_thr;

	bool QueueIsEmpty()
	{
	    return (_list.size() == 0);
	}
	bool QueueIsFull()
	{
	    return (_list.size() == _cap);
	}
	bool QueuePush(MyTask *t)
	{
	    _list.push(t);
	}
	void QueuePop(MyTask **t)
	{
	    *t = _list.front();
	    _list.pop();
	}
	static void *thr_start(void *arg)
	{
	    while(1)
	    {
		ThreadPool *p = (ThreadPool*)arg;
		pthread_mutex_lock(&p->_mutex);
		MyTask *task;
		while(p->QueueIsEmpty())
		{
		    //没有任务,则工作线程陷入等待
		    pthread_cond_wait(&p->_empty,&p->_mutex);
		}
		p->QueuePop(&task);
		pthread_mutex_unlock(&p->_mutex);
		pthread_cond_signal(&p->_full);
		task->Run();
		delete task;
	    }
	    return NULL;   
	}
    public:
	ThreadPool(int thr = 5,int q = 10)
	    :_max_thr(thr), _cap(q)
	{
	    pthread_mutex_init(&_mutex,NULL);
	    pthread_cond_init(&_full,NULL);
	    pthread_cond_init(&_empty,NULL);
	}

	~ThreadPool()
	{
	    pthread_mutex_destroy(&_mutex);
	    pthread_cond_destroy(&_full);
	    pthread_cond_destroy(&_empty); 
	}
	bool Init()
	{
	    int ret,i;
	    pthread_t tid;
	    for(i = 0; i < _max_thr; i++)
	    {
		ret = pthread_create(&tid,NULL,thr_start,(void*)this);
		if(ret != 0)
		{
		    printf("create thread error\n");
		    return false;
		}
	    }
	    pthread_detach(tid);
	    return true;
	}
	bool AddTask(MyTask *task)
	{
	    pthread_mutex_lock(&_mutex);
	    while(QueueIsFull())
	    {
		pthread_cond_wait(&_full,&_mutex);
	    }
	    QueuePush(task);
	    pthread_mutex_unlock(&_mutex);
	    pthread_cond_signal(&_empty);
	    return true;
	}
};

int main()
{
    ThreadPool p;
    p.Init();
    MyTask *t;
    int i = 0;
    while(1)
    {
	printf("add task data:%d\n",i);
	t = new MyTask(i++);
	p.AddTask(t);
    }
    return 0;
}

线程安全的单例模式:

单例模式:典型设计模式的一种,指一个对象只能被初始化一次
单例模式: 饿汉/懒汉模式
饿汉:启动阶段一次性初始化完毕,用户体验不好,因为程序初始化时间过长,但是后续运行流畅
懒汉:将初始化分摊到各个阶段,使用的时候再初始化,启动节点用户体验比较好,但是第一次运行到某个模块的时候,流畅度不太好。

class obj
{
	static T* data;  //饿汉模式
	obj() 			 //懒汉模式
	{
		mutex.lock();
		if(data == NULL)
		{
			data = new T();
		}
		mutex.unlock();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

quchen528

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值