"拉住童年的手,进行告别"
上篇主要谈论了,如何使用pthread库,进行线程的创建、等待....
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界自娱的代码,就叫做临界区互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源.原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完 成
我们先在实现一个抢票线程,可以让我们更好的理解,线程安全的一系列问题。
然而当票抢成负数是我们不希望看到的!
对于一张共享资源的ticket票,任何执行流,都能看到。因此这个ticket也叫做临界资源。
很显然,这样一种区域,是不安全的。因为任何执行流都可以在任何情况下对其进行更改。
从而导致,不同执行流拿到的数据是不一样。
由图可见,不管是ticket++ 还是ticket--,难以保证原子性。出现各执行流拿到的数据不一致的情况!
正因如此,就需要程序员,自己去保证线程运行,是安全的的情况。让各个执行流达到一种共同遵守,一次只有一个执行流,看到共享资源(临界资源)。
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
这样就可以保证,只一个执行流进入临界区,其余执行流会因为没拿到锁,阻塞在锁外。
本质上,锁需要被保护,才能去保护临界资源。而本质上,锁的实现,满足原子性的特性,是能够保证自己的安全。
#include
销毁;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
初始化;int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。可重入函数:一个执行流还没有执行完,就有其他执行流进来。但运行结果不会发生改变。
1.函数是可重入的,那就是线程安全的.2.函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题.3.如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的.
1.可重入函数是线程安全函数的一种
2.线程安全不一定是可重入的,而可重入函数则一定是线程安全的
执行流占用资源不释放,从而处于一种永久等待的状态;
1.互斥条件
2.请求保持
3.不剥夺条件
4.循环等待
可以知道,一个线程互斥地访问临界资源的时候,其他线程除了等待该线程访问完临界资源,释放锁之外,什么也做不了。
本质就是把进程、线程放入等待队列, 把它们的运行状态 设置R —>S,挂起等待。
在用户视角来看,就是被阻塞。
互斥锁需要各个执行流进行争抢得到。难免会有竞争能力强的执行流,反复申请锁,释放锁,但却不会做什么有效的事情,致使其他竞争力相对弱的执行流无法拿到锁,处于一饥饿状态!
所谓同步:保证数据安全的前提下,让线程 进行按序等待 访问临界资源~
有这个能力处理 线程同步问题的,叫做 条件变量。
#include
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
cond:要初始化的条件变量attr:NULL
#include
int pthread_cond_destroy(pthread_cond_t *cond);
#include
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
cond:在这个条件变量上等待;
mutex;互斥量(进行 释放)否则 别的线程进不来
#include
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有在等待的线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个 出在序列前列的进程
三种关系 两种角色 一个场所;“遵循的321”原则;
当然生产消费者模型还有其他优点:
①支持并发
②支持忙闲不均.
1.关系;
生产者生产者:竞争
消费者消费者:竞争
消费者生产者:互斥+同步
2.交易场所:内存中的一份缓冲区
在进程间地址通信的时候提到过信号量,但是是在 system V提过的。
他们作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。特别的,POSIX信号量 也可以用于线程同步。
在前面章节也已经说过,所谓信号量,本质就是一个计数器。描述这临界资源的内容。
其作用,就是为了实现同步互斥+更细腻度 对临界资源进行管理!
申请到 意味着 拥有使用 这份临界资源的权限!
申请信号量 意味着 信号量里的计数器 -- ;反之 就 ++
#include int sem_init(sem_t *sem, int pshared, unsigned int value);参数:pshared:0 表示线程间共享,非零表示进程间共享value :信号量初始值
int sem_destroy(sem_t *sem);
功能:等待信号量,会将信号量的值减 1int sem_wait(sem_t *sem);
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加 1 。int sem_post(sem_t *sem);
框架; 只需要让把sem 设置为1
执行任务;
①保证线程安全的两步:同步+互斥
②互斥:pthread_mutex_t lock 互斥锁
③同步:pthead_cond_t cond 条件变量
④POSIX信号量
本篇就到此结束 感谢你的阅读
祝你好运~