操作系统:12 线程竞争与线程池

1 基本概念

① 竞争与同步

        同一个进程中的线程能够共享进程中的绝大多数资源,当他们随意竞争时可能会导致共享资源被破坏、脏数据、不完整、不一致等问题

        通过一些方法让进程中的线程在竞争资源时相互协调,避免出现以上问题,这种手段就称为线程同步技术

② 临界区:

        能够被多个线程访问但又不能同时访问的代码片段称为临界区

③ 临界资源:

        能够被多个进程访问但又不能同时防伪的资源叫做临界资源

        当准备访问临界区和临界资源时,有别的线程和进程正在访问,那么会进入等待

④ 竞态条件:

        当多个线程在临界区内执行且等待,由于线程执行的顺序有随机性,导致结果不确定,就称发生了竞态条件

⑤ 原子操作:

        操作不能够被继续拆解、执行过程中不能被任何方式影响的操作,称为原子操作

2 互斥量(互斥锁)

    注意:linux系统man手册中没有pthread_mutex_xxx系列函数说明,需要额外安装

    sudo apt-get install manpages-posix-dev

    pthread_mutex_t 是一种互斥量数据类型,用于定义互斥量变量

    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

        功能:初始化一个互斥量

        mutex:要初始化的互斥量

        attr:写NULL使用默认属性即可

        注意:当互斥锁初始化后,默认是开锁状态

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

        注意:可以直接使用PTHREAD_MUTEX_INITIALIZER给互斥锁初始化

    int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:对一个互斥锁上锁

        mutex:要上锁的互斥量

        返回值:

                成功则上锁并返回继续执行

                失败则阻塞等待,直到互斥锁开锁并成功上锁才返回继续

    int pthread_mutex_trylock(pthread_mutex_t *mutex);

        功能:尝试对一个互斥锁加锁

        mutex:尝试要上锁的互斥量

        返回值:成功返回0,失败返回EBUSY(别线程再整加锁使用) 使用EBUSY加头文件errno.h

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

        功能:对一个互斥锁进行解锁

        mutex:要解锁的互斥量

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    功能:销毁一个互斥锁

3 信号量

        与XSI机制中的信号量原理相似,相当于线程之间使用的计数器,用于控制访问有限的共享资源的线程数

    sem_t是一种信号量数据类型,用于定义信号量变量

    int sem_init(sem_t *sem, int pshared, unsigned int value);

        功能:初始化信号量

        sem:要初始化的信号量

        pshared:信号量适用范围

                0 只能在本进程使用

                非0 表示该信号量可以被多个进程共享使用(Linux不支持)

        value:最多同时访问共享资源的线程数量

    int sem_wait(sem_t *sem);

        功能:对信号量-1,如果信号量的值为0则阻塞等待

    int sem_trywait(sem_t *sem);

        功能:对信号量尝试-1,如果成功,如果信号量值为0则返回EAGAIN

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

        功能:对信号量-1,如果信号量值为0则等待一段时间,超时后返回ETIMEOUT

    int sem_post(sem_t *sem);

        功能:对信号量+1

    int sem_destroy(sem_t *sem);

        功能:销毁信号量

4 死锁

4.1 什么是死锁

        多个进程/线程在使用共享资源时,相互等待对方手中的资源,在得到新资源前不会释放自己手上的资源,最终形成循环等待,这种现象称为死锁

4.2 产生死锁的4大必要条件

① 资源互斥:资源只有两种状态:可用和不可用状态,不能同时使用,同一时刻只能被一个进程或线程使用

② 占有且请求:已经得到了一些资源的进程或线程,继续请求新的资源,并持续占用旧资源

③ 资源不可剥夺:资源在分配给线程或进程后,不能被其它进程或线程强制性夺取,除非占用者主动放弃

④ 环路等待:当死锁发生时,系统中必定存在两个或两个以上的进程线程的执行路线形成环状

    注意:死锁一旦形成,现在的操作系统无法解决已出现的思索情况,因此只能预防死锁的产生

4.3 预防死锁的产生:破坏死锁条件产生的其中一个条件

① 破坏资源互斥:

        想办法让资源共享:共享或增加资源

        缺点:受代码环境或者资金影响无法让资源共享

② 破坏占有且请求:

        采用静态预分配机制,进程或线程可以再运行前一次性申请所有的资源,在资源没有得到全部满足前,不投入运行

        缺点:系统资源会被严重浪费,因为有些资源可能开始时使用,有些资源结束时才使用

③ 破坏资源不可剥夺:

        当一个进程或线程在占用了一个不可被剥夺的资源,并且请求新资源得不到满足时,就释放自己手中的所有资源,等待一段时间后,再重新申请资源

        缺点:该策略实现难度较高,并且释放已或得的资源会导致前一阶段的工作全部失效,反复的申请释放资源会增大CPU的系统开销,浪费内存、时间。

④ 破坏环路等待:

        给每个资源进行编号,进程或线程按照同一个编号顺序依次申请资源,并且只有拿到当前编号的资源后,才能去继续请求下一个编号的资源

        缺点:资源数多,编号难度较高,并且受到实际使用资源顺序情况影响,当增加或删除资源时,又需要重新设计编号方案

4.4 如何判断死锁位置

        画出整个程序的资源分配图

        简化资源分配图

        通过死锁原理:找出出现环路的位置

推荐使用:银行家算法

5 条件变量

        通过条件变量,让线程满足某些条件时,可以自己进入睡眠,也可以在某些条件满足时被其他线程唤醒,一般配合互斥锁

        pthread_cond_t 是一种条件变量数据类型,用于定义条件变量变量

    int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

        功能:初始化一个条件变量

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    注意:可以直接使用PTHREAD_COND_INITIALIZER给条件变量初始化

    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

        功能:让调用者睡入条件变量cond,并解锁mutex

    int pthread_cond_signal(pthread_cond_t *cond);

        功能:唤醒睡入cond的其中一个线程

        注意:睡前的互斥锁必须在唤醒前处于打开状态,才可被唤醒并自动加锁

    int pthread_cond_broadcast(pthread_cond_t *cond);

        功能:唤醒所有睡入cond的线程

        注意:睡前的互斥锁必须在唤醒前处于打开状态,才可被唤醒并自动加锁

    int pthread_cond_destroy(pthread_cond_t *cond);

        功能:销毁条件变量

7 生产者消费者模型

        生产者:产生数据的线程

        消费者:使用数据的线程

        仓库:临时存储数据的缓冲区,为了解决生产消费不协调的问题

可能产生的问题:

        生产快于消费:仓库爆满,撑死

        消费快于生产:仓库空虚,饿死

利用条件变量解决问题:

        当仓库缓冲区满的时候,生产者线程睡入条件变量(full),通知消费者线程全部醒来(empty)

        当仓库缓冲区空的时候,消费者线程睡入条件变量(empty),通知生产者线程全部醒来(full)

        根据这个消费者生产者模型,我们可以设计出一个线程池,这是我已经封装好的线程池,有需要可以前往参考:

c语言——线程池https://download.csdn.net/download/snowysc/87774803

你可能感兴趣的:(操作系统,linux,线程池,操作系统,c,线程同步)