栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Linux入门篇——多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux入门篇——多线程

文章目录
        • 概念
        • 线程的优点
        • 线程缺点
        • 线程异常
        • 线程用途
        • 操作:线程控制
          • 相关函数:
            • 创建新线程
            • 线程ID和LWP的关系
            • 线程等待
            • 线程终止
            • 线程分离
          • 线程安全

概念

线程是在一个进程内部运行的一个执行分支(执行流),属于进程的一部分,粒度比进程更加细和轻量化
线程在进程的地址空间中运行,可以直接被CPU调度(CPU调度时只看PCB,每一个PCB曾经被指派过执行方法和数据)
Linux中用进程的PCB来模拟线程,没有专门为线程设计TCB(不用维护复杂的进程和线程的关系,不用单独为线程设计算法,直接使用进程的一套相关的方法 )
内核视角:
进程是资源分配的基本单位,是承担分配系统资源的基本实体
线程是CPU调度的基本单位,承担进程资源的一部分的基本实体
进程划分资源给线程

Linux线程与接口关系的认识
Linux因为是用进程模拟的,所以Linux下不会提供直接操作线程的接口,而是给我们提供,在同一个地址空间内创建PCB的方法,从而分配资源给指定的PCB的接口–>对用户不友好–>系统级别的工程师,在用户层对Linux轻量级进程接口(创建线程的接口、释放线程、等待线程等)进行封装,将其打包成库,让用户直接使用库接口,即原生线程库(pthread库)(位于用户层)

所有的轻量级进程(“线程”)都是在进程内部运行的(地址空间:表示所能看到的大部分资源)
进程:具有独立性,只有部分共享资源(管道、IPC资源)
线程:大部分资源是共享的(共享地址空间,因此代码段、数据段都是共享的;文件描述符表;每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数);当前工作目录;用户id和组id),只有小部分资源是“私有”的(pcb,栈(线程之间临时数据不会互相干扰),(调度)上下文(形成自己在CPU寄存器中的临时数据),线程ID,一组寄存器,errno,信号屏蔽字(block表),调度优先级)

线程的优点

(1)创建一个新线程的代价要比创建一个新进程小得多 :不用创建PCB、地址空间、不用加载代码和数据,只需要加载和分配
(2)与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 :只需要将线程之间的上下文做切换,不需要切换页表、不需要更新各种缓存,数据一直有效
(3)线程占用的资源要比进程少很多 :不需要申请资源
(4)能充分利用多处理器的可并行数量 (多进程、多线程都有)
(5)在等待慢速I/O操作结束的同时,程序可执行其他的计算任务 (多进程、多线程都有)
(6)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现 :计算密集型应用,如加密、大数据运算等,主要使用CPU资源
tips:计算密集型应用线程不是越多越好:如果线程太多,会导致线程间被过度调度切换,这也是有成本的,一般线程数少于CPU核数
(7)I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作 :I/O密集型应用,如网络下载,云盘,ssh,在线直播,看电影,主要使用内存和外设的I/O资源
tips:计算密集型应用线程也不是越多越好,但IO允许多一些线程

线程缺点

(1)性能损失(多进程、多线程都有)
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
(2)健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的:一个线程崩溃会导致多个线程崩溃,甚至进程崩溃
(3)缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响:可能引起误修改
(4)编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

线程异常

(1)单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
(2)线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

线程用途

(1)合理的使用多线程,能提高CPU密集型程序的执行效率
(2)合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

操作:线程控制 相关函数: 创建新线程
 #include 
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数:pthread_t *thread:pthread库线程ID;const pthread_attr_t *attr:线程属性,设为NULL;void *(* start_routine) (void *):线程的回调函数,表明该线程执行的是进程的哪一部分; void *arg:给回调函数传入的参数

// pthread_create函数实例
#include
#include
#include
void *thread_run(void* args){
  const char *id = (const char *)args;
  while(1){
    printf("我是%s线程,%dn",id,getpid());
    sleep(1);
  }
}
int main(){
  pthread_t tid;
  pthread_create(&tid,NULL,thread_run,(void *)"thread 1");
  while(1){
    printf("我是main线程,%dn",getpid());
    sleep(1);
  }
  return 0;
}

一个进程,但是有两个执行流
ps -aL:出现LWP(light wait process轻量级进程)选项,os根据LWP进行调度

一个进程的多个线程并称为一个线程组,组ID=当前进程PID

线程ID和LWP的关系

线程ID的本质就是在动态库中被加载到内存之后,动态库中该线程的属性信息的起点地址,LWP是Linux内核中采用的对应到用户层的数值,以此用进程模拟线程

pthread_self():获取当前线程tid


创建多个线程

#define NUM 5
void* thread_run(void* args){

  int num = *(int*)args;
  while(1){  
    printf("我是新线程[%d],tid = %lun",num,pthread_self());
    sleep(10);
    if(num == 3){
      printf("thread 3 quitn");
      // 野指针 验证一个线程崩溃使多个线程崩溃
      int *p = NULL;
      *p = 1000;
    }
  }
}

int main(){
  //创建多个线程
  pthread_t tid[NUM];
  for(int i = 0;i
    pthread_create(tid+i,NULL,thread_run,(void*)&i);
    sleep(1);
  }
  while(1){
    printf("我是主线程,tid = %lun",pthread_self());

    printf("################begin#############n");
    for(int i = 0;i
      printf("我创建了线程[%d],tid = %lun",i,tid[i]);
    }
    printf("#################end##############n");
    sleep(1);
  }
}

线程等待
#include 
int pthread_join(pthread_t thread, void **retval);

参数:pthread_t thread:要等待的线程tid; void **retval:输出型参数,用来获取新线程退出时函数的返回值

#define NUM 1
void* thread_run(void* args){
  int num = *(int*)args;
  while(1){  
    printf("我是新线程[%d],tid = %lun",num,pthread_self());
    sleep(5);
    break;
  }
  return (void*)12;
}

int main(){
  pthread_t tid[NUM];
  for(int i = 0;i
    pthread_create(tid+i,NULL,thread_run,(void*)&i);
    sleep(1);
  }
  void* status = NULL;

  pthread_join(tid[0],&status);
  printf("线程返回值 = %dn",(int)status);
}

线程终止

(1)函数中return
main函数退出retun时,代表主线程以及进程退出
其他线程函数return,只代表当前线程退出
(2)pthread_exit终止自己

void pthread_exit(void *retval);


注意:exit()是使某个进程退出,不能用于退出单个线程
(3)pthread_cancel取消目标线程(可以实现新线程取消主线程)

int pthread_cancel(pthread_t thread);

该函数的返回值成功=0,不成功=!0

被取消的进程退出码=-1,
系统定义:#define PTHREAD_CANCELED -1

线程分离

不想等待的处理方法:线程分离,分离之后的线程不需要被join等待,运行完毕之后,会自动释放Z

// 在想要分离的线程函数开头调用该接口
int pthread_detach(pthread_t thread);

设置分离后的进程不能再设置join
应用场景:主线程不退出。新线程处理业务完毕再退出

线程安全

定义:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题
原因:多个线程共享地址空间,共享资源(优点:通信方便;缺点:缺少访问控制)
线程安全问题:因为一个线程的操作问题,给其他线程造成了不可控的影响,或者引起崩溃、异常、逻辑不正确等现象,称为线程安全问题
如何避免:不使用全局、stl、malloc堆空间、new等会在全局内有效的数据,使用的话需要有访问控制(比如互斥、同步)(局部变量不影响,因为每个线程有自己独立的栈结构)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1032780.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号