互斥变量不一定要是全局变量, 只要多个线程都能访问到就行了
#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)
{
//我们想让代码在这个位置阻塞
//等待链表中有了结点之后, 再继续往下走
//使用了条件变量, 阻塞线程
}
//链表不为空的处理代码
生产者和消费者模型:一个场所,两种角色,三种关系
功能:解耦和,支持忙闲不均,支持并发
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
设计模式:大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,
单例模式:
饿汉模式: 程序初始化时进行实例化,因为资源已经全部加载,因此运行速度快,缺点是初始化时间较长
懒汉模式: 资源使用的时候进行加载,对象使用的时候实例化,优点是初始化时间短,但是运行时不流畅