• 【Linux】线程概念与线程控制


    认识线程

    线程是一个执行流(运行代码,处理数据)

    ​ 1.操作系统使用pcb来描述一个程序的运行-------pcb就是进程

    ​ 2.linux下通过pcb模拟实现线程,因此linux下的线程是一个轻量级进程
    ​ 3.这个轻量级进程因为公用大部分进程资源;相较于传统进程更加轻量化
    进程是资源分配的基本单位----因为程序运行时资源分配给整个线程组(线程是cpu调度的基本单位-----因为linux pcb是线程)
    线程之间资源的独有与共享
    ​ 独有:寄存器(自己的上下文数据[上文:处理过的数据;正文:正在处理的数据;下文:即将处理的数据]), 信号屏蔽字(那个线程拿到时间片就会处理信号,若不想让该线程处理某信号,则在该线程信号屏蔽),errno(每个线程都有自己的errno,防止errno改正后,统一被修改),线程ID,调度优先级

    ​ 共享:共享虚拟地址空间(共享代码段和数据段),文件描述符表(一个线程打开文件获取文件描述符可以直接传给另一个线程,另一个线程直接进行文件操作),当前工作路径信号处理方式(信号处理方式一致,但是具体哪一个进程去处理是不一定的),用户id/组id

    多进程与多线程的优缺点分析

    多线程处理任务的优点:

    • 线程间通信特别方便,除了进程间的通信方式,还可以通过全局变量/传参(共享资源)
    • 创建和销毁一个线程的成本相比于进程要更低
    • 线程间调度相较于进程要更低

    多线程处理任务的缺点:

    • 线程之间缺乏访问控制,有些系统调用/异常针对的是整个进程;稳定性相比于进程更低。场景:比如创建shell这种对主程序稳定安全性要求较高的程序,就需要用多进程,让子程序来背锅
    线程控制

    线程创建/线程终止/线程等待/线程分离
    由于linux下操作系统并没有提供线程的控制系统调用接口,封装了一个线程控制接口库。使用库函数实现创建的线程我们称之为用户态线程,这个用户态线程在内核中使用一个轻量级进程实现调度。
    POSIX线程库

    • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
    • 要使用这些库函数,要通过引入头文件<pthread.h>
    • 链接这些线程函数库时,要使用编译器命令的"-lpthread"选项
      linux下的线程:用户态线程+轻量级进程
    线程创建
    //int pthread_create(pthread_t * thread,const pthread_attr_t *attr,
    //void* (start_routine)(void*) ,void* args);
    //thread:用来获取线程id
    //attr:用来设置进程属性以及栈的大小,通常置为NULL;
    //start_routine:线程的入口函数
    //arg:传递给进程入口函数的参数
    //返回值:0表示成功,!0表示不成功
    #include 
        2 #include <unistd.h>
        3 #include <pthread.h>
        4 
        5 //线程创建,体会每个线程都是执行流
        6 void* thr_entry(void* arg)
        7 {
        8   while(1)
        9   {
       10     printf("i am common thread-----%s\n",(char*)arg);
       11     sleep(1);                                                                                                     
       12   }
       13   return NULL;
       14 }
       15 
       16 int main()
       17 {
       18   //int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
       19   //void* (*start_routine)(void*),void* args)
       20   pthread_t tid;
    W> 21   char* param = "this is input param";
       22   int ret = pthread_create(&tid,NULL,thr_entry,(void*)param);
       23   if(ret != 0)
    24   {
       25     printf("pthread create error\n");
       26     return -1;
       27   }
    W> 28   printf("tid:%p\n",tid);
       29   while(1)
       30   {
       31     printf("i am main thread-----\n");
       32     sleep(1);
       33   }
       34   return 0;
       35 }                                                                                                                 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述
    ps -efL //可以查看轻量级进程
    线程中的id讨论:
    ​tid : 线程地址空间首地址,方便用户操作线程
    ​pcd->pid 轻量级进程id(LWP)
    pcb->tgid 进程(线程组)id,默认等于首线程地址id

    线程终止
    //return   不能在main函数中return(退出的是进程-----导致所有线程退出)
    //void pthread_exit(void * retval);        退出线程自身,谁调用谁退出//retval:线程退出的返回值
    //int pthread_cancel(pthread_t thread);取消其他线程,让其他线程退出//thread:要取消的线程ID
    //线程退出后,默认不会自动释放资源,(保存自己的退出结果在线程独有的地址空间中),会造成资源泄露
    //主线程退出,其他线程依然可以正常运行
        1 #include <stdio.h>                                                                                                
        2 #include <unistd.h>
        3 #include <pthread.h>
        4  
        5 void* thr_entry(void* arg)
        6 {
        7   //pthread_exit(NULL)退出当前线程
        8   pthread_cancel((pthread_t) arg);          
        9   pthread_t ctid;
       10   ctid = pthread_self();              
       11   while(1){                                                 
    W> 12     printf("i am common pthread--------%p\n",ctid);
       13     sleep(1);
       14   }                                  
       15   return NULL;
       16 }  
       17 int main()               
       18 {                                    
       19   pthread_t mtid;
       20   mtid = pthread_self();//获取本线程的ip地址
       21   pthread_t tid;
    W> 22   char* param = "this is input param";
       23   int ret = pthread_create(&tid,NULL,thr_entry,(void*)mtid);
          24   if(ret != 0)                                     
       25   {          
       26     printf("pthread create error\n");
       27     return -1;
       28   }
    W> 29   printf("tid:%p\n",tid);
       30   //pthread_cancle(tid); 取消普通线程
       31   while(1){      
       32     printf("i am main thread-------\n");    
       33     sleep(1);   
       34   }                                   
       35 }                                                                                                                 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述

    线程等待

    概念:等待指定线程退出,获取这个线程的退出返回值,并回收这个线程的资源。
    ​ 一个线程有默认属性:joinable;处于joinable属性的线程退出后为了保存返回值,因此不会自动释放资源
    ​ 如果不进行等待会造成内存泄漏(一个线程只有处于joinable状态的时候,才需要被等待)
    ​ pthread_join(pthread_t tid,void **retval);

    线程分离

    是将线程的joinable属性修改为detach属性
    ​ 线程若处于detach属性,则线程退出后将自动回收资源;并且这个线程不需要等待,因为线程返回值占用的空间已经被回收。
    pthread_detach(pthread_t tid)
    线程分离的适用场景:
    一进入自己的线程就线程等待:pthread_detach(pthread_self());

    #include 
        2 #include <unistd.h>
        3 #include <errno.h>
        4 #include <pthread.h>
        5 
        6 //线程等待
        7 void* thr_entry(void* arg)
        8 {
        9   pthread_detach(pthread_self());
       10   sleep(3);
    W> 11   char* ptr = "hello";
       12   pthread_exit(ptr);                                                                                              
       13   pthread_cancel((pthread_t)arg);
       14   while(1)
       15   {
       16     printf("i am common thread----%s\n",(char*)arg);
       17     sleep(1);
       18   }
       19 }
       20 
       21 int main()
       22 {
       23   pthread_t mtid;
    #include 
        2 #include <unistd.h>
        3 #include <errno.h>
        4 #include <pthread.h>
        5 
        6 //线程等待
        7 void* thr_entry(void* arg)
        8 {
        9   pthread_detach(pthread_self());
       10   sleep(3);
    W> 11   char* ptr = "hello";
       12   pthread_exit(ptr);                                                                                              
       13   pthread_cancel((pthread_t)arg);
       14   while(1)
       15   {
       16     printf("i am common thread----%s\n",(char*)arg);
       17     sleep(1);
       18   }
       19 }
       20 
       21 int main()
       22 {
       23   pthread_t mtid;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这里插入图片描述

    线程安全

    多个线程同时对临界资源进行访问而不会造成数据二异性

    如何实现线程安全:

    同步:时序合理性

    互斥:对临界资源同一时间的访问唯一性
    线程间互斥实现:互斥锁

    • 1.定义互斥锁变量
    pthread_mutex_t
    
    • 1
    • 2.对互斥锁变量进行初始化
    pthread_mutex_init(mutex,attr);//mutex : 互斥锁变量//attr: 初始化互斥锁属性---通常置NULL
    
    • 1
    • 2
    • 3
    • 3.对临界资源操作之前先加锁 [在对临界资源操作之前加锁],若可以加锁则直接修改计数,函数返回;否则挂起等待
    pthread_mutex_lock(&mutex);
    pthread_mutex_trylock   //  pthread_mutex_timedlock
    
    • 1
    • 2
    • 4.对临界资源操作完毕后进行解锁 [在对临界资源操作之后立即解锁,线程退出之前也要解锁]
    	pthread_mutex_unlock(&mutex)
    
    • 1

    5.销毁互斥锁

    	pthread_mutex_destory(&mutex)
    
    • 1

    死锁:多个线程对锁资源进行竞争访问,但是因为推进顺序不当导致相互等待,以至于程序无法往下运行
    死锁产生的四个必要条件
    1.互斥条件:一个锁只有一个线程可以获取

    2.不可剥夺条件:我上的锁别人不可以解

    3.请求与保持条件:拿着A锁去请求B锁,若获取不到B锁,也不会释放A锁。

    4.环路等待条件:A拿着A锁去请求B锁,B拿着B锁去请求A锁。

    死锁预防:避免这4个必要条件
    死锁避免:死锁检测算法,银行家算法

    线程同步

    线程同步的实现:等待+唤醒
    操作不满足等待条件,别人促使等待条件满足后唤醒等待

    条件变量
    条件变量实现同步:用户在对临界资源访问之前,先判断是否能够操作,若可以,线程直接操作;若不能,条件变量提供等待条件,让pcb在等待队列上。其他线程促使操作条件满足,唤醒条件变量等待队列上的线程。

    1.定义条件变量:pthread_cond_t cond
    
    2.条件变量初始化:pthread_cond_init(&cond,&attr)
    
    3.用户在判断条件不满足的情况下提供等待功能 pthread_cond_wait(&cond,&mutex);
    为什么条件变量要与互斥锁一起使用:线程什么时候等待,需要一个判断条件;这个判断条件也是临界资源(等待了之后,其他线程需要促使这个条件满足(修改临界资源));因此这个临界资源的操作就需要受保护(默认使用互斥锁实现保护)
    
    4..用户在促使条件满足条件后,唤醒等待 pthread_cond_signal (唤醒至少一个线程) 	        
    pthread_cond_broadcast(唤醒所有线程)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程
    LC-396. 旋转函数(前缀和+滑动窗口、动态规划)
    使用pycharm远程调试
    机器学习-模型评估与选择(第2章)课后习题
    Scala基础【常用方法补充、模式匹配】
    前端八股文85-141
    Selenium自动化测试之学会元素定位
    机器学习总结四:逻辑回归与反欺诈检测案例
    OpenHarmony分布式购物车案例展示~
    数据可视化的常见工具
  • 原文地址:https://blog.csdn.net/qq_41943585/article/details/97680337