学习视频链接
黑马程序员-Linux系统编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1KE411q7ee?p=167
目录
一、同步概念
1.1 线程同步
线程1有以下功能:买菜、烧菜、洗碗
线程2有以下功能:洗菜、切菜、端菜
线程1完成买菜,线程2才能执行洗菜、切菜,接着是线程1烧菜,线程2端菜,线程1洗碗
1.2 线程互斥
访问临界资源会互斥进入(参考操作系统)
1.3 数据混乱原因
1、资源共享(独享资源则不会)
2、调度随机(意味着数据访问会出现竞争)
3、线程间缺乏必要的同步机制
以上 3 点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱
所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥
二、互斥量mutex
2.1 基本概念
Linux 中提供一把互斥锁 mutex(也称之为互斥量)
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁
资源还是共享的,线程间也还是竞争的,但通过 “锁” 就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了
但,应注意:同一时刻,只能有一一个线程持有该锁
当 A 线程对某个全局变量加锁访问,B 在访问前尝试加锁,拿不到锁,B 阻塞。C 线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱
所以,互斥锁实质上是操作系统提供的一把 “建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定
因此,即使有了 mutex,如果有线程不按规则来访问数据,依然会造成数据混乱
2.2 出现的问题
没有进程同步
2.3 使用流程
1、主要函数
pthread_mutex_init 函数
pthread_mutex_destroy 函数
pthread_mutex_lock 函数
pthread_mutex_trylock 函数
pthread_mutex_unlock 函数
以上 5 个函数的返回值都是:成功返回 0,失败返回错误号
pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待
pthread_mutex_t mutex; 变量 mutex 只有两种取值 1、0
2、使用 mutex(互斥量、互斥锁)一般步骤
(1) pthread_ mutex_t lock; 创建锁
(2) pthread_mutex_init; 初始化
(3) pthread_mutex_lock; 加锁
(4) 访问共享数据 (stdout)
(5) pthrad_mutext_unlock; 解锁
(6) pthead_mutex_destroy; 销毁锁
2.4 代码
2.5 互斥锁的使用技巧
1、代码写成下面这样,解锁比较靠后
代码执行的时候在一段时间一直是小写或者大写,原因是子线程有锁的时候很难让父线程拿到锁,或者父线程有锁的时候很难让子线程拿到锁
要尽量保证锁的粒度,越小越好。(访问共享数据前,加锁。访问结束立刻解锁)
2、互斥锁,本质是结构体,我们可以看成整数,初值为 1。(pthread_mutex_init() 函数调用成功)
加锁:--操作,阻塞线程
解锁:++操作,唤醒阻塞在锁上的线程
try锁:尝试加锁,成功 -- 、失败 返回错误号,不阻塞
三、读写锁
3.1 读写锁特性(操作系统会讲)
1、读写锁是 “写模式加锁” 时,解锁前,所有对该锁加锁的线程都会被阻塞
2、读写锁是 “读模式加锁” 时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。。
3.读写锁是 “读模式加锁” 时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
读写锁也叫共享独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享
读写锁非常适合于对数据结构读的次数远大于写的情况
3.2 主要函数
pthread_rwlock_init 函数
pthread_rwlock_destroy 函数
pthread_rwlock_rdlock 函数
pthread_rwlock_wrlock 函数
pthread_rwlock_tryrdlock 函数
pthread_rwlock_trywrlock 函数
pthread_rwlock_unlock 函数
以上 7 个函数的返回值都是:成功返回 0,失败直接返回错误号
pthread_rwlock_t 类型 用于定义一个读写锁变量
pthread_rwlock_t rwlock;
3.3 代码
在一个 write 执行期间有其他的 write 到来后会再次先执行 write,而不是比 write 先到的 read,因为写锁的优先级更高
四、死锁
是使用锁不恰当导致的现象
1、对一个变量反复lock
2、两个线程,各自持有一把锁,请求另一把(操作系统占有等待)
五、条件产量
5.1 定义
条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所
5.2 主要应用函数
1、简介
pthread_cond_init 函数
pthread_cond_destroy 函数
pthread_cond_wait 函数
pthread_cond_timedwait 函数
pthread_cond_signal 函数
pthread_cond_broadcast 函数
以上 6 个函数的返回值都是:成功返回 0, 失败直接返回错误号
pthread_cond_t 类型 用于定义条件变量
pthread_cond_t cond;
2、pthread_cond_init 函数
(1) 初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr 表条件变量属性,通常为默认值,传 NULL 即可
(2) 也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
3、pthread_cond_destroy 函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
4、pthread_cond_wait 函数
阻塞等待一个条件变量
int pthread cond_ wait(pthread cond t *restrict cond, pthread mutex t *restrict mutex);
函数作用:
(1) 阻塞等待条件变量 cond (参1) 满足
(2) 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
(1) (2) 两步为一个原子操作(原子操作是不可分的,一次性完成)
(3) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
5、pthread_cond_signal()
唤醒阻塞在条件变量上的(至少)一个线程
6、pthread_cond_broadcast()
唤醒阻塞在条件上的所有进程
5.3 初始化使用宏函数代替
1、动态初始化
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
2、静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
5.4 生产者消费者问题
1、一个生产者一个消费者
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
struct msg {
int num;
struct msg *next;
};
struct msg *head;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义/初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; // 定义/初始化一个条件变量
void err_thread(int ret, char *str)
{
if (ret != 0) {
fprintf(stderr, "%s:%s\n", str, strerror(ret));
pthread_exit(NULL);
}
}
void *produser(void *arg) // 生产者进程
{
while (1) {
struct msg *mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1; // 模拟生产一个数据
printf("------ produce: %d\n", mp->num);
pthread_mutex_lock(&mutex); // 加锁 互斥量
mp->next = head; // 写公共区域
head = mp;
pthread_mutex_unlock(&mutex); // 解锁 互斥量
pthread_cond_signal(&has_data); // 唤醒阻塞在条件变量has_data上的线程
sleep(rand() % 3);
}
return NULL;
}
void *consumer(void *arg) // 消费者进程
{
while (1) {
struct msg *mp;
pthread_mutex_lock(&mutex); // 加锁 互斥量
if (head == NULL) {
pthread_cond_wait(&has_data, &mutex); // 阻塞等待条件变量
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&mutex); // 解锁 互斥量
printf("+++++ consumer: %d\n", mp->num);
free(mp);
sleep(rand() % 3);
}
return NULL;
}
int main(void)
{
int ret;
pthread_t pid, cid;
srand(time(NULL));
ret = pthread_create(&pid, NULL, produser, NULL);
err_thread(ret, "pthread_create produser error");
ret = pthread_create(&cid, NULL, consumer, NULL);
err_thread(ret, "pthread_create consumer error");
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
2、一个生产者多个消费者
一上来没有数据,所有的消费者进程阻塞在 pthread_cond_wait(&has_data, &mutex); 函数上,执行了一次生产者进程,所有的消费者进程同时被唤醒,去抢锁。
一个消费者进程抢到了锁,把生产者生产的内容消耗了,解除锁,另一个消费者进程拿到锁去读数据,但是读不到数据了。
所以应该先判断条件变量是否满足,如果满足再去拿锁,所以使用 while 循环,如下图
另外稍微修改了以下生产者进程
得到的结果:
六、信号量
6.1 作用
相当于初始化值为 N 的互斥量。N 值, 表示可以同时访问共享数据区的线程数
6.2 主要应用函数
sem_init 函数
sem_destroy 函数
sem_wait 函数
sem_trywait 函数
sem_timedwait 函数
sem post 函数
以上 6 个函数的返回值都是:成功返回 0,失败返回 -1, 同时设置 errno。(注意,它们没 pthread 前缀)
sem_t 类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)
sem_tsem; 规定信号量 sem 不能 < 0。头文件 <semaphore.h>
1、sem_init 函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参1:sem 信号量
参2:pshared 取 0 用于线程间,取非 0(一般为 1)用于进程间
参3:value 指定信号量初值
2、sem_destroy 函数
销毁一个信号量
int sem_destroy(sem_t *sem);
3、sem_wait 函数
给信号量加锁 --
int sem_wait(sem_t *sem);
4、sem_post 函数
给信号量解锁 ++
int sem_post(sem_t *sem);
5、sem_trywait 函数
尝试对信号量加锁 --(与 sem_wait 的区别类比 lock 和 trylock)
int sem_trywait(sem_t *sem);
6、sem_timedwait 函数
限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参2:abs_timeout 采用的是绝对时间
定时 1 秒:
time_t cur = time(NULL); 获取当前时间
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur + 1; 定时 1 秒
t.tv_nsec = t.tv_sec + 100;
sem_timedwait(&sem, &t); 传参
6.3 信号量基本操作
但,由于 sem_t 的实现对用户隐藏,所以所谓的 ++、-- 操作只能通过函数来实现,而不能直接++、-- 符号
信号量的初值,决定了占用信号量的线程的个数
6.4 代码
信号量实现 生产者 消费者问题