• 线程&线程控制


    线程基本概念:

    1 线程

      (1)概念:linux下没有真正的线程,所谓的线程都是通过进程的pcb模拟的,因此linux下的线程也称为“轻量级进程”,之前我们所说的进程现在看来,可以理解为:只有一个线程的线程组。也可以将线程理解为:在一个程序里的一个执行路线,更准确的来说,线程是一个进程内部的控制序列

      (2)进程:至少有一个线程的线程组

      (3)linux下的pcb是task_struct,task_struct中的tgid是线程组id,也等于第一个进程的pid,如果再创建新的线程,这些线程的tgid会相同

    2 线程和进程

      (1)进程是资源竞争的基本单位

      (2)线程是程序执行的最小单位

      (3)线程共享进程的数据,比如进程的正文段,数据段,文件描述符(代表一个线程打开文件,另一个线程也可以拿着文件描述在安全的前提下进行操作),信号的处理方式,当前工作目录,用户id,组id。但线程也有自己独有的资源:栈,上下文数据,errno全局变量,信号屏蔽字,调度优先级等等。(注:这些独有的数据都存放在虚拟地址空间的共享区)

      (4)进程和线程的关系如下图:

    3 线程的优点:

      (1)创建一个线程的代价要比创建一个线程的代价小的多

      (2)与进程之间的切换先比而言,线程之间的切换需要操作系统做的工作很少

      (3)线程占有的资源要比进程小很多

      (4)能充分利用多处理器的可并行数量

      (5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

      (6)线程多用于cpu密集型与I/O密集型程序

    4 线程的缺点:

      (1)性能损失:在有的情况下会增加额外的调度与同步开销(比如有的操作无法与其他线程共享处理器,当这种操作的数量大于处理器的数量时,会产生这种情况)

      (2)健壮性降低:线程切换之间缺乏安全保护,需要额外的操作保证线程对共享资源的安全访问

      (3)缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

      (4)编程难度较高

    线程控制

    注意:我们所使用的是大佬们封装的一套接口,因此所使用的都是库函数,所以也说这套接口创建的线程是用户态线程,并且这个用户态线程在操作系统中对应了一个轻量级进程

    1 线程创建

      int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

      参数:第一个参数返回线程id,第二个参数表示要设置线程的属性,第三个参数表示线程启动后要执行的函数,第四个参数表示创个线程启动函数的参数

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

    2 线程终止

      void pthread_exit(void *value_ptr);

      参数:输出型参数,带出线程退出时的返回值,不能讲局部变量的地址传给它;

      无返回值

      int pthread_cancel(pthread_t thread);

      功能:同一进程中的其他线程,可以取消另一个线程

      参数:线程id

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

    3 线程等待与分离

      (1)需要线程等待的原因:已经退出的线程,其空间没有释放,仍然在进程地址空间中,创建新的线程不会覆盖退出线程的空间,因此为了避免内存泄漏,需要线程等待,获取线程的退出状态,并且允许系统回收资源

      (2)int pthread_join(pthread_t thread, void** value_ptr)

        参数:第一个参数:表示需要等待线程的线程id,第二个参数是输出型参数,指向一个指针,表示线程的退出状态返回值,传NULL表示不关心返回状态

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

      注意:只有线程处于joinable状态,这个线程才能被等待,此状态是线程的默认属性;除此之外,还有detach状态,此状态表示线程退出后直接释放资源,线程无法被等待。线程的这两个属性是互斥的,二者只能选其一

      (3)分离线程:int pthread_detach(pthread_t thread)

        int pthread_detach(pthread_self())

    线程的同步与互斥

      大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程地址空间中,这种情况下,变量归单个线程,其他线程无法获得这种变量;但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互;多个线程并发的操作共享变量,会引发一些问题;因此需要通过同步与互斥实现一定的保护机制

    1 互斥量

      互斥:同一时间理解资源的唯一访问性

      互斥锁是原子操作 

      (1)定义胡互斥锁变量:pthread_mutex_t mutex

      (2)初始化变量:pthread_mutex_init(&mutex)

      (3)临界资源加锁/解锁

        pthead_mutex_lock(&mutex)

        pthread_mutex_unlock(&mutex)

        pthread_mutex_trylock(&mutex):非阻塞

        pthread_mutex_timelock(&mutex)

      (4)销毁互斥锁:

        pthread_mutex_destory(&mutex)

    2 条件变量

      当一个线程互斥的访问某个变量时,他可能发现在其他线程改变状态之前什么也做不了:例如当一个线程访问队列时,发现队列为空,它只能等待其他线程将一个节点添加到队列中,这种情况下需要条件变量

      (1)初始化

        pthread_cond_init(&cond)

      (2)销毁

        pthread_cond_destory(&cond)

      (3)等待条件满足

        pthread_cond_wait(&cond,&mutex)

        注意:为了保护条件的修改,所以我们使用了互斥锁进行保护,但是如果加锁之后条件不满足而陷入休眠,则造成死锁,因此在休眠之前先解锁,但是解锁和休眠应该是原子操作,,否则也可能会诱发死锁的情况出现

      (4)唤醒等待

        pthread_cond_signal(&cond)   只通知一个

        pthread_cond_broadcast(&cond)  唤醒所有等待的线程

    3 死锁产生的必要条件

      (1)互斥条件

      (2)不可剥夺条件

      (3)请求与保持条件

      (4)环路等待条件

    4 预防死锁

      破坏其中的一个条件

      银行家算法

      死锁检测算法

  • 相关阅读:
    C# 委托、事件,lamda表达式
    visual studio快捷键大全
    从零开始编写自己的C#框架(28)——建模、架构与框架
    从零开始编写自己的C#框架(27)——什么是开发框架
    从零开始编写自己的C#框架(26)——小结
    从零开始编写自己的C#框架(25)——网站部署
    科班出身和培训上岗的程序员谁更牛?
    女友眼中的IT男
    一千个选择python的理由
    亲身经历:程序人生路上的荆棘与感动
  • 原文地址:https://www.cnblogs.com/love-you1314/p/10457126.html
Copyright © 2020-2023  润新知