初识生产者消费者模型
举一个例子:
学生去超市消费的时候,与厂家生产的时候,两者互不相冲突。
生产的过程与消费的过程 – 解耦
临时的保存产品的场所(超时) – 缓冲区
模型总结“321”原则:
- 3种关系:生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者(互斥[保证共享资源的安全性] && 同步) – 产品(数据)
- 2种角色:生产者线程,消费者线程
- 1个交易场所:一段特定结构的缓冲区
只要我们想写生产消费模型,我们本质工作其实就是维护321原则!
特点:
- 生产线程和消费线程进行解耦
- 支持生产和消费的一段时间的忙闲不均的问题
- 提高效率
举例:
我们以前:main函数获取用户输入,然后调用fun函数,fun函数执行,打印结果。这是一个串行的流程,现在我们对应生产者消费者模型:
同步
条件变量
由于上述的原因,我们不能仅靠锁来去实现线程互斥。
什么是条件变量:
- 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。(比如我们抢票,如果目前还没有票,那么每个人都是做同样的动作:加锁→判断票是否为0→解锁,在还没有放票之前,每个人都什么也做不了)
- 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
感性认识条件变量:
模拟一个面试流程:
1.假如我们一堆人都要到一家公司面试,但是这个公司的HR组织的不行,每次面试都是一堆人举手来竞争面试机会,都说要自己先来,而HR也是看谁顺眼就让他进去面试 – 一份共享资源被多线程并发式的竞争
2.同样是面试,而HR组织的好,说要面试必须要到等待区等待,按照排队顺序来:(这个等待区就是一个条件变量)
当条件不满足的时候,我们线程必须去某些定义好的条件变量上进行等待!
初步使用
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
int tickets = 1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *start_routine(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // 为什么要有mutex,后面就说
// 判断暂时省略
std::cout << name << " -> " << tickets << std::endl;
tickets--;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
// 通过条件变量控制线程的执行
pthread_t t[5];
for (int i = 0; i < 5; i++)
{
char *name = new char[64];
snprintf(name, 64, "thread %d", i + 1);
pthread_create(t+i, nullptr, start_routine, name);
}
while (true)
{
sleep(1);
pthread_cond_signal(&cond);
//pthread_cond_broadcast(&cond);
std::cout << "main thread wakeup one thread..." << std::endl;
}
for (int i = 0; i < 5; i++)
{
pthread_join(t[i], nullptr);
}
return 0;
}
我们可以发现进程按照一定的顺序在执行
POSIX信号量
POSIX
信号量和SystemV
信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
销毁信号量
int sem_destroy(sem_t *sem);
等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
其他常见的各种锁
- 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
- 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
- CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁
场景一:你喊你的朋友一起去吃饭,你的朋友说等他一会就来,在等的期间你每隔一会就打电话问他来没来 – 自旋
场景二:你喊你的朋友一起去吃饭,你的朋友说等他一会就来,你不想就只在原地等,你走路去学校网吧上网,走路去的过程叫做挂起,在网吧上网叫做等待,你朋友跟你打电话说他已经来了,然后你回学校这叫做唤醒。-- 挂起等待
是什么决定了最终的等待方式?
等待的时间问题。已经被申请的临界资源决定了其他线程要等待,一个成功申请临界资源的线程在临界区内要待多少时间,这个时间长短就决定了我们使用哪种方式:(阻塞等待/自旋)。时间的长短如何定义?都是用分别测试效果。
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_unlock(pthread_spinlock_t *lock);
读写锁
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
场景:比如在以前学校的黑板报,画黑板报的为写者,写者与写者之间是互斥关系(你正在写字,他不能把你写的字擦了画画),而观看的同学是读者,读者与读者之间不存在什么关系(黑板报画好了,我们都是一起看的,不存在先来就先看其余的蒙着眼睛不准看)。
读者写者模型与生产者消费者模型本质区别是:消费者会拿走数据而读者不会。
读者写者:写者之间 – 互斥 、读写者 – 互斥/同步 、读者之间 – 没关系
读者写者模型适用场景:一次发布,很长时间不做修改,大部分时间都是被读取的(大部分时间都是被读写,少量的时间在进行写入)
设置读写优先
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
读者加锁:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写者加锁:
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
统一解锁:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
文章来源:https://www.toymoban.com/news/detail-624671.html
如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀文章来源地址https://www.toymoban.com/news/detail-624671.html
到了这里,关于【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!