线程间竞争

一、基本概念

    原子操作:中途不会被打断的操作称为原子操作(不会被其他线程竞争影响的操作)

    竞争与同步:

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

        通过一些方法让线程在竞争资源时相互协调,避免出现以上情况,这种线程间协同工作称为线程同步

    临界区和临界资源:

        被多个线程同时访问的代码称为临界区、被同时访问的资源称为临界资源

二、互斥量(互斥锁)

    pthread_mutex_t 是一种数据类型 可以定义互斥量变量

    int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict 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_trylock(pthread_mutex_t *mutex);

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

    返回值:成功返回0,失败返回错误编码

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    功能:对互斥量解锁

   

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    功能:销毁互斥量

三、信号量

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

    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,如果不够减则等待abs_timeout时间,如果超时返回ETIMEDOUT错误编码

    int sem_post(sem_t *sem);

    功能:对信号量+1

    int sem_destroy(sem_t *sem);

    功能:销毁信号量

四、死锁

    1、什么是死锁

        多个进程或线程之间互相等待对方手中的资源,在得到新的资源之前不会主动释放自己手中的资源,如果形成了等待环路,称之为死锁现象

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

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

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

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

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

    注意:以上四个条件同时成立就会形成死锁,死锁一旦产生基本无解,以现在的操作系统是无法解决死锁,因此只能防止死锁的产生

    3、如何防止死锁的产生

        破坏资源互斥:想办法让资源能够共享使用

            缺点:受现实环境和资金的影响无法让资源共享

        破坏占有且请求:

            采用预分配的方式,让进程或线程在运行前一次性申请所有资源,如果在资源没有满足时不投入运行

            缺点:系统资源的占用会严重浪费,因为有些资源可能开始时使用,但是有些资源可能最后才使用

        破坏资源不可剥夺:

            当一个进程或线程已经占有一个不可被剥夺的资源,并且在请求新资源无法被满足时,则释放已经占用的资源,等待一段时间后重新请求

            缺点:该策略实现比较麻烦,而且释放已经申请的资源可能会导致前一阶段的工作无效,反复地申请释放资源也会增加系统开销、占用CPU和寄存器、内存等资源

        破坏等待环路:

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

            缺点:资源的编号相对稳定,当资源增加或删除时受到很大影响

    4、如何判断死锁

        1、画出资源分配图

        2、简化资源分配图

        3、使用死锁判定原理:如果没有环路一定不会出现死锁

    了解:银行家算法

五、条件变量

    当某些条件满足时,可以让线程自己进入睡眠,也可以当某些条件满足时唤醒正在睡眠的线程

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

    功能:初始化条件变量

    cond:要初始化的条件变量

    attr:默认给NULL即可

    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_timedwait(pthread_cond_t *restrict cond,

           pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);

    功能:让当前线程睡入cond,并解锁mutex,只睡眠abstime时间,超时会被操作系统唤醒

    int pthread_cond_destroy(pthread_cond_t *cond);

    功能:销毁条件变量

    注意:使用条件变量可以实现生产者与消费者模型

六、生产者与消费者模型(线程池、数据池)

    生产者:产生数据的线程

    消费者:使用数据的线程

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

    可能产生的问题

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

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

   

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

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

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

    进程、线程相关的问题:

    1、进程与线程的区别

    进程:

        狭义定义:进程是正在运行的程序的实例

        广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

    线程:

        是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

    线程与进程的区别可以归纳为以下4点:

        1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

        2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

        3)调度和切换:线程上下文切换比进程上下文切换要快得多。

        4)在多线程OS中,进程不是一个可执行的实体。

    2、进程处理多任务时需要解决什么问题

    进程间通信:进程获取的内存空间是一块抽象的内存,然后会映射到实际的某一块物理内存,因此,每个进程都无法访问其他进程在内存里面数据。不同进程之间的通信,就意味着在两块不同的物理内存间传递数据。

    3、线程处理多任务时需要解决什么问题

    线程间竞争:

        竞争与同步:

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

        通过一些方法让线程在竞争资源时相互协调

        避免死锁的产生

    4、TCP服务端的编程模型有哪几种?以及它们的优缺点?

        同步阻塞迭代模型:

            优点:实现简单、占用资源少

            缺点:使用单进程,在accept、recv、send时都可能会发生阻塞,一次只能服务一个客户端,效率低

        多进程并发模型:

            优点:解决了一次只能服务一个客户端的问题

            缺点:看似有多个子进程同时工作,实际上只有一个CPU在处理,CPU轮流为每个进程服务一定时间,切换进程的过程中也会耗费一些时间,开销大

        多路复用模型:

            优点:不需要频繁地创建和销毁进程,从而节约了内存资源、时间资源,也避免了进程之间的竞争、等待

            缺点:要求单个客户端的任务不能太过于耗时,否则其他客户端就会感知到卡顿

        多线程并发模型:

            优点:开销小、速度快

            缺点:不利于资源管理和保护

    5、随着客户端的连接和退出越来越频繁,服务端都需要频繁地创建、销毁线程,该过程会比较耗时,如何解决?

        创建和销毁线程时间相对于任务执行时间较短的操作不可忽略

        创建线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值