线程同步与互斥(线程安全)

同步 : 临界资源的合理访问

互斥 : 临界资源同一时间唯一访问

互斥变量不一定要是全局变量, 只要多个线程都能访问到就行了

互斥锁

#include 

pthread_mutex_t mutex; 	//创建一把互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex,//初始化互斥锁
				const pthread_mutexattr_t *restrict attr);
restrict : 可加可不加, 加了restrict那么attr指向的区域不能被其他同类型指针访问

//锁定互斥锁, 并检测当前互斥锁是否锁定, 如果当前没有锁定, 就锁定并返回0,否则阻塞等待				
int pthread_mutex_lock(pthread_mutex_t *mutex);

//尝试锁定, 如果当前没有锁定, 就锁定并返回0, 否则返回错误号
int pthread_mutex_trylock(pthread_mutex_t *mutex);

//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
		
互斥锁特点:
		1.多个线程访问共享数据的时候是串行的
使用互斥锁的缺点:
		1.效率低
互斥锁的使用步骤:
		1.创建互斥锁: pthread_mutex_t mutex;
		2.初始化互斥锁: pthread_mutex_init(&mutex, NULL) : --mutex = 1
		3.寻找共享资源, 在操作共享资源前加锁
				pthred_mutex_lock(&mutex) : --mutex = 0
		4.解锁
				pthread_mutex_unlock(&mutex): --mutex = 1
临界区:
		在lock和unlock之间的被锁住的区域叫做临界区, 临界区越大代码执行效率越差
		实际开发中临界区应越小越好
加锁的原因:
		1.模拟原子操作
		2.线程同步
必要条件:
		1.互斥条件, (我操作时别人不能操作)
		2.不可剥夺, (我加的锁别人不能解锁)
		3.请求与保持条件, (拿着手里的, 请求其他的, 其他的请求不到, 也不放开手里的)
		4.环路等待条件
产生的场景:
		1.加锁解锁顺序不同
		预防死锁: 破坏必要条件
		避免死锁: 死锁检测算法, 银行家算法 
死锁处理:
		1.自己锁自己
		2.资源数大于锁数, (开发应用中,应该有几个共享资源就对应有几把锁)
		3.	线程1对共享资源A加锁成功
			线程2对共享资源B加锁成功
			
			线程1访问共享资源B, 对B加锁----线程1阻塞在B锁上
			线程2访问共享资源A, 对A加锁----线程2阻塞在A锁上
		
			如何解决:
			---让线程按照一定的顺序去访问共享资源
			---在访问其他锁的时候, 需要先将自己的锁解开
			---trylock方式加锁

读写锁

1.读写锁是一把锁
	pthread_rwlock_t lock;
2.读写锁的类型:
	读锁 : 对内存做读操作
	写锁 : 对内存做写操作
3.读写锁的特性
	1.线程A加读锁成功, 又来了三个线程, 做读操作, 可以加锁成功
		---读共享, 可以并行处理
	2.线程A加写锁成功, 又来了三个线程, 做读操作, 三个线程阻塞
		---写独占
	3.线程A加读锁成功, 又来了B线程加写锁阻塞, 又来了C线程加读锁阻塞
		---读写不能同时进行
		---写的优先级高
4.读写锁场景练习
	线程A加写锁成功, 线程B请求读锁
		---线程B阻塞
	线程A持有读锁, 线程B请求写锁
		---线程B阻塞
	线程A拥有读锁, 线程B请求读锁
		---线程B请求读锁成功
	线程A拥有读锁, 然后线程B请求写锁, 然后线程C请求读锁
		---线程B和C都阻塞(写的优先级高, C的优先级与B比较后, 阻塞)
		---A解锁, B成功C阻塞
		---B解锁, C成功
	线程A持有写锁, 然后线程B请求读锁, 然后线程C请求写锁
		---B和C阻塞
		---A解锁, C成功B阻塞
		---C解锁, B成功
5.读写锁的适用场景
		互斥锁 : 读写串行
		读写锁 :
				读 : 并行
				写 : 串行
		程序中的读操作远多于写操作时,适用读写锁
6.主要操作函数
#include 

//初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
	const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//加读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//尝试加读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//加写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//尝试加写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

实现同步的方法 : 临界资源访问合理性—生产出来才能使用 没有资源则等待, 生产资源后唤醒等待

条件变量

1.	条件变量不是锁,它是能阻塞线程的函数
	条件变量+互斥锁实现线程同步
	互斥锁 : 保护一块共享数据
	条件变量: 引起阻塞
	---生产者和消费者模型
2.条件变量的两个动作
	条件不满足 : 阻塞线程
	当条件满足 : 通知阻塞的线程开始工作
3.条件变量的类型 : pthread_cond_t;
4.主要函数:
	初始化一个条件变量
	int pthread_cond_init(pthread_cond_t *restrict cond,
			const pthread_condattr_t *restrict attr);
	销毁一个条件变量
	int pthread_cond_destroy(pthread_cond_t *cond);
	阻塞等待一个条件变量
	int pthread_cond_wait(pthread_cond_t *restrict cond,
			pthread_mutex_t *restrict mutex);
	限时等待一个条件变量
	int pthread_cond_timedwait(pthread_cond_t *restrict cond,
			pthread_mutex_t *restrict mutex,
			const struct timespec *restrict abstime);
	唤醒至少一个阻塞在条件变量上的线程
	int pthread_cond_signal(pthread_cond_t *cond);
	唤醒全部阻塞在条件变量上的线程
	int pthread_cond_broadcast(pthread_cond_t *cond);
	
不是什么时候都能阻塞线程
情景 : 
	链表 Node *head = NULL;
	while(head == NULL)
	{
		//我们想让代码在这个位置阻塞
		//等待链表中有了结点之后, 再继续往下走
		//使用了条件变量, 阻塞线程
	}
	//链表不为空的处理代码
	
生产者和消费者模型:一个场所,两种角色,三种关系
功能:解耦和,支持忙闲不均,支持并发

信号量(信号灯)posix标准下

1.头文件--semaphore.h
2.信号量类型
		sem_t sem;
		加强版的互斥锁
3.主要函数
	初始化信号量
		#include 
		
		int sem_init(sem_t *sem, int pshared, unsigned int value);
		
		Link with -pthread.
		0---线程同步
		1---进程同步
		value---最多有几个线程操作共享数据
	销毁信号量
		int sem_destroy(sem_t *sem);
	加锁
		int sem_wait(sem_t *sem);
			调用一次相当于对sem做了--操作
			如果sem值为0, 线程会阻塞
	尝试加锁
		int sem_trywait(sem_t *sem);
			sem==0, 加锁失败, 不阻塞, 直接返回
	限时尝试加锁
		int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
	解锁
		int sem_post(sem_t *sem);
			对sem做了++操作

信号量与条件变量的区别:信号量具有资源计数功能,临界资源是否能够操作通过自身技术判断
条件变量需搭配互斥锁一起使用
信号量还能实现互斥,计数仅为0/1

设计模式:大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,
单例模式:
饿汉模式: 程序初始化时进行实例化,因为资源已经全部加载,因此运行速度快,缺点是初始化时间较长
懒汉模式: 资源使用的时候进行加载,对象使用的时候实例化,优点是初始化时间短,但是运行时不流畅

你可能感兴趣的:(Linux)