• 网络编程day6——基于C/S架构封装的线程池


    一、线程竞争基本概念

        竞争与同步

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

            通过一些手段让线程在竞争资源时相互协调、避免出现以上问题,这就称为线程同步

        原子操作

            操作过程中不能被打断的操作称为原子操作

        临界资源、临界区、竞态条件:

            能够被多个进程访问但是又无法同时访问的资源称为临界资源

            每个进程中访问临界资源的那段代码称为临界区,能够被多个线程访问但是又无法同时访问的代码片段

            多个线程在临界区内执行时,由于线程的执行顺序具有随机性,从而导致结果不确定,称为发生了竞态条件

    二、互斥量(互斥锁)

        man手册中没有全部的posix手册文档,需要安装

        sudo apt-get install manpages-posix-dev

        pthread_mutex_t 是一种数据类型,用来定义互斥锁变

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

        功能:对互斥量进行初始化,完成后锁处于开锁状态

        mutex:要初始化的互斥量

        attr:互斥量的属性设置,一般默认给NULL即可

       

        也可以在定义时通过PTHREAD_MUTEX_INITIALIZER初始化互斥量

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

        int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:对互斥量加锁,成功则返回继续执行,失败则阻塞休眠等待,直到互斥量解锁并成功加锁才唤醒返回

        int pthread_mutex_unlock(pthread_mutex_t *mutex);

        功能:对互斥量解锁,对别人上锁的互斥量解锁,失败返回EBUSY

        int pthread_mutex_trylock(pthread_mutex_t *mutex);

        功能:对互斥量尝试加锁,成功返回0 失败返回EBUSY,立即返回

        int pthread_mutex_destroy(pthread_mutex_t *mutex);

        功能:销毁互斥量

    三、读写锁

        读写锁将线程访问共享数据时发出的请求分为两种:

            读请求:只读取共享数据,不修改

            写请求:存在修改共享数据的行为

            1、当有多个线程同时发出读请求,可以同时执行

            2、当有多个线程同时发出写请求,只能一个一个的执行

            3、当发出读请求的线程正在执行时,发出写请求的线程必须等待前面所有读请求线程执行完后才能执行

            4、当发出写请求的线程正在执行时,发出读请求的线程必须等待前面所有写请求线程执行完后才能执行

        当读写锁被发出读请求的线程占用时,称为"读锁",当读写锁被发出读请求的线程占用时,称为"写锁"

            1、当读写锁未被任何线程占用,称为"无锁",发出读请求、写请求的线程都可以占用,如果同时请求,默认优先给读请求占用

            2、当变成读锁(多个线程占用)时,读请求的线程可以占用不会阻塞,但是写请求的线程会阻塞等待读锁解锁

            3、当变成写锁(单个线程占用)时,读、写请求的线程都会阻塞等待写锁解锁

            pthread_rwlock_init 初始化读写锁

            pthread_rwlock_wrlock 写锁上锁

            pthread_rwlock_rdlock 读锁上锁

            pthread_rwlock_unlock 解锁

            pthread_rwlock_destroy 销毁

       

    四、自旋锁

        在任意时刻最多只能有一个线程获取该锁

        对于互斥量,如果资源已经被占用,申请者会进入休眠态

        对于自旋锁,则不会引起申请者休眠,而是一直在申请处进入循环不停地查看自旋锁是否解锁,直到解锁并加锁成功后才退出循环往下执行

        一般情况下使用互斥量,但是如果知道被锁住的代码执行时间很短(或者是主动让其变短),那么应该选择使用系统开销(运行态<->休眠态)较少的自旋锁更合适,因为运行态进入休眠态时,需要切换内核态 有点耗时

    五、信号量

        与进程间通信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,成功返回0

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

        功能:对信号量减1,如果值为0,则等待一段时间,超时还没能减则返回ETIMEDOUT

        int sem_post(sem_t *sem);

        功能:对信号量的值加1

        int sem_destroy(sem_t *sem);

        功能:销毁信号量

    六、死锁

        1、什么是死锁

            多个进程或线程相互等待对方的资源,在得到新资源前不会释放自己的旧资源,这样就形成了循环等待,该现象称为死锁

        2、产生死锁的四大必要条件

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

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

            资源不可剥夺:当资源已经分配给进程或线程后,不能被其他进程或线程强制性获取,除非占用者主动释放

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

            注意:一旦产生死锁基本无解,现在的操作系统无法解决死锁,因此只能防止死锁的产生

        3、防止死锁产生的方法

            破坏互斥条件:

                想办法让资源能够共享

                缺点:受到环境或资金的影响无法让资源共享

            破坏占用且请求:

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

                缺点:可能会导致系统资源的昂费,因为有些资源是很靠后才会使用,但是已经提前分配占用,导致其它不会产生死锁的进程或线程也无法使用

            破坏资源不可剥夺:

                当一个进程或线程占用了不可被剥夺的资源,并且请求新资源无法得到满足时,那么就把全部占用的资源主动释放,过一段时间后重新开始

                缺点:该策略实现难度高,释放已经占用的资源会导致前一阶段的工作失效,还要反复申请释放资源,浪费资源、时间

            破坏环路等待:

                给每个资源进行编号,进程或线程都按照编号顺序来请求资源,并且必须拿到前一个编号资源后,才能去拿后一个

                缺点:资源的编号顺序要相对稳定,否则在运行期间资源的增加或删减都会让该过程受极大的影响

        4、如何判断死锁

            1、画出资源分配图

            2、简化资源分配图

            3、使用死锁定理判断:看有没有环路

    七、条件变量

        当某些设置的条件满足时可以让线程自己进入睡眠态,也可以在另一些设置的条件满足时可以被其它线程唤醒,需要配合互斥量使用

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

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

        功能:初始化条件变量,也可以直接使用PTHREAD_COND_INITIALIZER在定义时初始化

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

        int pthread_cond_destroy(pthread_cond_t *cond);

        功能:销毁条件变量

        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_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

        功能:让当前线程睡入cond,最多睡abstime时间,超时也会返回

        使用条件变量和互斥量可以实现 生产者和消费者模型

    八、生产者与消费者模型 (C/S模型)

        生产者:生产数据的线程

        消费者:使用数据的线程

        仓库:临时存储数据的缓冲区(仓库解决生产、消费不匹配的问题)

        可能产生的问题:

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

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

        利用条件变量来解决以上问题:

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

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

    应用:利用生产者消费者模型构建 线程池

    线程池的封装(gitee)

  • 相关阅读:
    游戏合作伙伴专题:BreederDAO 与 Ultiverse 建立了一个新的元宇宙
    AtCoder Beginner Contest 263 G.Erasing Prime Pairs(二分图最大匹配-网络流)
    大数据Doris(一):Doris概述篇
    Ai-WB2-32S在window下使用vs 和 msys2编译以及烧录
    Sparse稀疏检索介绍与实践
    Sheet保存到本地 dataList前8行不读取
    (一)NIO 基础
    国家网络安全周 | 天空卫士CEO刘霖在《中国网信》杂志发表署名文章
    以太坊合并后,区块链生态将发生什么改变?
    模仿CSDN黑暗帝国动态背景的vue项目(附源码)
  • 原文地址:https://blog.csdn.net/weixin_51479108/article/details/132721756