【hello Linux】线程概念

news2024/12/31 3:40:41

目录

1. 线程概念的铺设 

2. Linux线程概念

2.1 什么是线程

2.2 线程的优点

2.3 线程的缺点

2.4 线程异常

2.5 线程用途

3. Linux进程VS线程

4. Linux线程控制

4.1 POSIX线程库

4.2 创建线程

4.3 进程ID和线程ID

4.4 线程终止

4.5 线程等待

4.6 分离线程


Linux🌷 

1. 线程概念的铺设 

在我们之前的学习中都是一个进程对应一个执行流,也就是说进程是OS分配资源的基本单位,也是CPU调度的基本单位;

今天,我们便要学习一个进程对应一个执行流,或者是一个进程对应多个执行流的情况,这里的每个执行流便可称为一个线程;

也就是说一个进程内是可能存在多个线程的,进程与线程数之比=1:n;内核中有可能存在着大量的线程,OS便要对这些线程进行管理。“先描述再组织”是OS管理对象的一个准则,线程是通过线程控制块(TCB)描述的,这是常规的一些操作系统的做法,比如Windows;

在Linux中是没有专门为线程设计TCB的,而是用进程PCB来模拟线程。

下面用一张图来大概说明下线程与进程:

 对于线程来说只创建task_struct,将当前进程的资源(代码+数据)划分为若干份让每个task_struct用,每个task_struct就是一个需要被调度的执行流;对于CPU来说,此时看到的task_struct是小于原先的task_struct的,这里的task_struct也成为轻量级进程;多个线程是共享同一进程的地址空间的;

这样做不用维护复杂的进程和线程的关系,不用单独为线程设计任何算法,直接使用进程的一套相关的方法,OS只需要聚焦在线程间的资源分配上就好了;

进程的今昔对比:

  • 之前的进程,内部只有一个执行流;
  • 今天的进程,内部可以具有多个执行流;

Linux线程与接口关系的认识:

Linux中的线程是用进程模拟的,Linux中没有提供直接操作线程的接口,只是提供了在同一地址空间内创建task_struct的方法,分配资源给指定的task_struct的接口,这种方法对用户特别不友好,系统级别的工程师在用户层,对Linux轻量级进程接口进行了封装,给我们打包成了库,让用户直接使用库函数,这个库称为 原生线程库 是用户层的;

2. Linux线程概念

2.1 什么是线程

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的控制序列”;
  • 一切进程至少都有一个执行线程;
  • 线程在进程内部运行,本质是在进程地址空间内运行;
  • Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化;
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流;

2.2 线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多;
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多;
  • 线程占用的资源要比进程少很多;
  • 能充分利用多处理器的可并行数量;
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现;
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作;

在这里要注意的一点是:

对于计算密集型应用,并不是说线程越多是越好的,一般线程的个数与CPU的核数相等即可,如果线程太多会导致被过度调度切换(有成本的),假如只有一个CPU划分了10个线程,这样的话还不如让一个进程直接在CPU上运行;

 对于IO密集型应用,IO是允许多一些线程,如果一个进程需要等待磁盘资源、IO资源,那么便可以将该进程分为多个线程,让等待这些资源的时间重叠,从而缩短整个等待时间。但也不是说越多越好的,因为磁盘只有一个,划分为再多的线程也只有排队等待同一个磁盘资源,无济于事;

2.3 线程的缺点

1. 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计

算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指

的是增加了额外的同步和调度开销,而可用的资源不变。

2. 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

3. 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响。
4. 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

2.4 线程异常

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

2.5 线程用途

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

3. Linux进程VS线程

1. 进程和线程

  • 进程是资源分配的基本单位;
  • 线程是CPU调度的基本单位;
  • 线程共享进程数据,但也拥有自己的一部分数据:
    • 线程ID
    • 一组寄存器
    • errno
    • 信号屏蔽字
    • 调度优先级

在这最重要的是:栈和上下文;

栈保存临时数据,上下文用于CPU的调度切换; 

2. 进程的多个线程共享同一地址空间,因此Text SegmentData Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表;
  • 每种信号的处理方式(SIG_ IGNSIG_ DFL或者自定义的信号处理函数);
  • 当前工作目录;
  • 用户id和组id;
3. 进程和线程的关系如下图:

 4. Linux线程控制

4.1 POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的;
  • 要使用这些函数库,要通过引入头文<pthread.h>;
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项;

4.2 创建线程

功能:创建一个新的线程

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

参数
    thread:输出型参数,返回线程ID;
    attr:设置线程的属性,attr为NULL表示使用默认属性;
    start_routine:是个函数地址,线程启动后要执行的函数;
    arg:传给线程启动函数的参数;

返回值:成功返回0;失败返回错误码
错误检查:
  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误;
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回;
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小;
  #include <stdio.h>
  #include <pthread.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/types.h>
  
  void *thread_run(void *arg)
  {
    int i;
    for(;;)
    {
      printf("I am %s,pid:%d\n",(char*)arg,getpid());                                                    
      sleep(1);
    }
  }
  int main()
  {
    //用于接收新创建的线程的id
    pthread_t tid;
    //用于接收创建线程函数的返回值
    int ret;
    //创建线程
    if((ret=pthread_create(&tid,NULL,thread_run,(void*)"thread1"))!=0)
    {
      printf("pthread_create:%s\n",strerror(ret));
      return 1;
    }
    //主执行流
    int i;
    for(;;)
    {
      printf("I am main thread!pid:%d\n",getpid());
      sleep(1);
    }
    return 0;
  }

 4.3 进程ID和线程ID

  • Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)
  • 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就变成了1N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?
  • Linux内核引入了线程组的概念
struct task_struct {
    ...
    pid_t pid;
    pid_t tgid;
    ...
    struct task_struct *group_leader;
    ...
    struct list_head thread_group;
    ...
};
  • 多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述符中的tgid,含义是Thread Group ID,该值对应的是用户层面的进程ID;

 查看线程ID:

ps -aL

强调一点:

线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。

在程序中,我们也可以使用函数的方式获得该线程自身的ID:

#include <pthread.h>

pthread_t pthread_self(void);

 该函数获得的线程ID和刚才查出的线程ID是不同的,我们查到的线程ID是 pthread 线程库的线程ID,使用命令的方式查看的是Linux内核中的LWP,pthread库的线程ID是一个虚拟地址,如下图中的pthread_t id所示;

 4.4 线程终止

如果需要只终止某个线程而不终止整个进程 , 可以有三种方法:
  • 从线程函数return。这种方法对主线程不适用,main函数return相当于调用exit(终止整个进程);
  • 线程可以调用pthread_ exit终止自己;
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程;
pthread_exit函数:
功能:线程终止

原型:
    void pthread_exit(void *value_ptr);

参数:
    value_ptr:value_ptr不要指向一个局部变量。

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意 ,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配
, 不能在线程函数的栈上分配, 因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数:
功能:取消一个执行中的线程

原型:
    int pthread_cancel(pthread_t thread);

参数:
    thread:线程ID

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

4.5 线程等待

线程等待的原因:

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

pthread_join函数:

功能:等待线程结束

原型:
    int pthread_join(pthread_t thread, void **value_ptr);

参数:
    thread:线程ID;
    value_ptr:输出型参数,用来获取新线程推出时候的函数的返回值;

返回值:成功返回0;失败返回错误码
调用该函数的线程将挂起等待 , 直到  id  为  thread  的线程终止。 thread 线程以不同的方法终止 , 通过
pthread_join  得到的终止状态是不同的,总结如下:
  • 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值;
  • 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED((void*)-1)
  • 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数;
  • 如果对thread线程的终止状态不感兴趣,可以传NULLvalue_ ptr参数;
  #include <stdio.h>    
  #include <pthread.h>    
  #include <unistd.h>    
  #include <stdlib.h>    
      
  void *thread1( void *arg )    
  {    
    printf("thread 1 returning ... \n");    
    int *p = (int*)malloc(sizeof(int));    
    *p = 1;    
    return (void*)p;    
  }    
      
  void *thread2( void *arg )    
  {    
    printf("thread 2 exiting ...\n");    
    int *p = (int*)malloc(sizeof(int));    
    *p = 2;    
    pthread_exit((void*)p);    
  }    
      
  void *thread3( void *arg )    
  {    
    while(1)    
    {    
      printf("thread 3 is running ...\n");    
      sleep(1);    
    }    
    return NULL;    
  }                                                                                                                                        
      
  int main()    
  {    
    pthread_t tid;    
    void *ret;    
      
    //thread1 return     
    //创建线程1    
    pthread_create(&tid, NULL, thread1, NULL);    
    //等待线程1    
    pthread_join(tid,&ret);    
    printf("thread return, thread id:%X, return code:%d\n",tid,*(int*)ret);    
    free(ret);    
      
    //thread2 exit    
    //创建线程2    
    pthread_create(&tid, NULL, thread2, NULL);    
    //等待线程2
    pthread_join(tid,&ret);
    printf("thread return, thread id:%X, return code:%d\n",tid,*(int*)ret);
    free(ret);
  
    //thread3 cancel by other
    //创建线程3
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid,&ret);
    if(ret==PTHREAD_CANCELED)
      printf("thread return, thread id:%X, return code:PTHREAD_CANELED\n");
    else 
      printf("thread return, thread id:%X, return code:NULL\n",tid);
    return 0;
  }
在这里要提的一点是:

线程对于整个程序来说,根本上也是用函数的形式呈现的;

函数退出时有三种情况:

  • 1. 代码跑完结果正确;
  • 2. 代码跑完结果不正确;
  • 3. 代码异常;

对于上述3种情况,我们只 join 前两种,因为线程代码异常的话,OS会发信号给进程,整个进程就瘫痪了,无需考虑此情况;

4.6 分离线程

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏;
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_run(void *arg)
{
  pthread_detach(pthread_self());
  printf("%s\n",(char*)arg);
  return NULL;
}

int main()
{
  pthread_t tid;
  if(pthread_create(&tid, NULL, thread_run, (void*)"thread1")!=0)                                       
  {
    printf("create thread error!\n");
    return 1;
  }

  int ret=0;
  //很重要,让新创建的线程先分离,在等待
  sleep(1);

  if(pthread_join(tid,NULL)==0)
  {
    printf("pthread wait success!\n");
    ret=0;
  }
  else
  {
    printf("pthread wait failed!\n");
    ret=1;
  }
  return ret;
}

如果本篇博客对您有所收获的话,还请点赞👍、收藏🤏加关注🎈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/483114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA时序约束(六)时序例外约束

系列文章目录 FPGA时序约束&#xff08;一&#xff09;基本概念入门及简单语法 FPGA时序约束&#xff08;二&#xff09;利用Quartus18对Altera进行时序约束 FPGA时序约束&#xff08;三&#xff09;时序约束基本路径的深入分析 FPGA时序约束&#xff08;四&#xff09;主时…

解除Word的编辑保护【简单版】

首先&#xff0c;我们遇到的情况是下图这样的&#xff1a; 点一下停止保护&#xff0c;是下图这样的&#xff1a; 开始解决&#xff1a; 第一种方式&#xff1a;另存为Word Xml 存好了&#xff0c; 打开如下图&#xff08;我用VS打开的&#xff0c;最好找个能够格式化代码的编…

C++---区间DP---棋盘分割(每日一道算法2023.5.2)

注意事项&#xff1a; 涉及到"矩阵/二维前缀和"的一些知识&#xff0c;建议先理解那篇文章。 题目&#xff1a; 将一个 88 的棋盘进行如下分割&#xff1a;将原棋盘割下一块矩形棋盘并使剩下部分也是矩形&#xff0c;再将剩下的部分继续如此分割&#xff0c;这样割了…

echarts数据可视化-动态柱状图

效果如下&#xff1a; 此处用的echarts柱状图为&#xff1a;Axis Align with Tick 本文的要讨论的内容&#xff1a; 1、柱状图样式修改 2、多数据的缩放展示 柱状图样式修改 // 数据 const city reactive([{ value: 335, name: 长沙 },{ value: 310, name: 武汉 },{ value: …

C++类和对象 ——构造函数

C拷贝构造函数详解 什么是拷贝构造函数&#xff1f;拷贝构造函数的特征默认拷贝构造函数为什么需要显示定义构造函数&#xff1f;拷贝构造函数的调用场景什么时候不需要自己定义拷贝构造函数 什么是拷贝构造函数&#xff1f; 在现实生活中&#xff0c;拷贝构造函数就好像我们上…

Linux服务器 容器化部署新版Jenkins

安装Docker 先安装yml yum install -y yum-utils device-mapper-persistent-data lvm2设置加速镜像&#xff08;阿里云镜像&#xff09; sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo安装docker yum -y install d…

智慧工厂人员定位系统源码,实现对工厂内的人车、物、料等的精确定位

智慧工厂人员定位系统源码 技术架构&#xff1a;Java vue spring boot 系统概述&#xff1a; 采用UWB定位技术&#xff0c;通过在厂区内布设一定数量的定位基站&#xff0c;实时精确地定位员工、车辆、物品上微标签位置&#xff0c;零延时地将人、车、物的位置信息显示在工厂…

数据结构(六)—— 二叉树(2)遍历

文章目录 递归三要素一、深度优先遍历&#xff08;前中后序&#xff09;1.1 递归遍历1.1.1 前序&#xff08;中左右&#xff09;1.1.2 中序&#xff08;左中右&#xff09;1.1.3 后序&#xff08;左右中&#xff09; 1.2 迭代遍历1.2.1 前序1.2.2 后序1.2.3 中序 二、广度优先遍…

创建前、中、后序二叉树

创建前、中、后序二叉树 一、前序二叉树二、中序二叉树二、后序二叉树 一、前序二叉树 规则&#xff1a;根->左->右 前序遍历结果&#xff1a;ABCDEFGHK 二、中序二叉树 规则&#xff1a;左->根->右 中序遍历结果&#xff1a;ABCDEFG 二、后序二叉树 规则&a…

浅尝ChatGPT使用之Python字典嵌套排序

一、背景 所负责的项目从v1.0升级到v2.0之后&#xff0c;发送到kafka的Json数据字段顺序和内容有所改变&#xff0c; v1.0版本推送数据样例&#xff1a; {"name": "小王子","author": "安托万德圣-埃克苏佩里&#xff08;1900-1944&#…

1.Hive基础

1.简介 作用&#xff1a;将结构化数据映射为一张表&#xff0c;并提供类sql功能 本质&#xff1a;将HQL转化成MapReduce程序 &#xff08;1&#xff09;Hive处理的数据存储在HDFS ​ &#xff08;2&#xff09;Hive分析数据底层的实现是MapReduce ​ &#xff08;3&#x…

keil5固件库版本的工程建立

keil5固件库版本的工程建立 一、一个文件夹&#xff0c;如图再建立4个文件夹 二、准库往上图四个文件夹里粘贴 从标准库里面把Libraries里面的两个文件夹全部复制到新建文件夹Libraries里面 三、来对新建的Libraries里面的两个文件夹进行更改 STM32F10x_StdPeriph_Driver这个…

ajax与json

title: 15 ajax与json date: ‘2023-3-29’ 从一个例子开始 传统的方式进行前后端交互是什么样子的&#xff1f; <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <html> <head><me…

python cms建站教程:Wagtail建站(二、修改主页与自定义后台管理)

不得不说python的中文cms建站教程实在是太少了&#xff0c;直接用Django/Flask这样的框架从头开始写又实在是有点麻烦&#xff0c;自己摸索着写一点使用Wagtail建站的方法&#xff0c;仅供参考。Wagtail是一款基于Django框架的CMS建站工具&#xff0c;可以为你的网站提供一个比…

点赋科技:本地生活,如何开启复苏之路

目前&#xff0c;全球经历这场前所未有的疫情大流行已经结束&#xff0c;尽管许多国家和地区的经济和社会都受到了影响。然而&#xff0c;做好本地生活的复苏规划和推进&#xff0c;将有助于在疫情之后尽快走出经济低迷期&#xff0c;恢复社会活动和生活体验。点赋科技将阐述如…

初识MySQL数据库——“MySQL数据库”

各位CSDN的uu们你们好呀&#xff0c;小雅兰好久没有更文啦&#xff0c;确实是心有余而力不足&#xff0c;最近学习的内容太难了&#xff0c;这篇博客又是小雅兰的新专栏啦&#xff0c;主要介绍的是一些MySQL数据库的知识点&#xff0c;下面&#xff0c;让我们进入初识MySQL数据…

【黑马程序员 C++教程从0到1入门编程】【笔记8】 泛型编程——模板

https://www.bilibili.com/video/BV1et411b73Z?p167 C泛型编程是一种编程范式&#xff0c;它的核心思想是编写通用的代码&#xff0c;使得代码可以适用于多种不同的数据类型。 而模板是C中实现泛型编程的一种机制&#xff0c;它允许我们编写通用的代码模板&#xff0c;然后在需…

静态成员与友元函数

有缘 class Point {private:double x, y; public:Point(double xx, double yy) ;friend double Distance(Point &a, Point &b); };Point::Point(double xx, double yy) {x xx;y yy; }double Distance(Point &a, Point &b) {return sqrt(pow(a.x - b.x, 2) p…

【STM32CubeMX】F103定时中断

前言 本文记录下我学习STM32CubeMX时的流程&#xff0c;方便以后回忆。系统板是基于STM32F103C6T6。本章记录定时中断。 步骤 实验目标&#xff1a;利用定时器TIM2装载计数&#xff0c;1S的定时中断事件&#xff0c;事件是LED(PC13)的亮灭。 配置时钟源为外部高速源(HSE),流程…

MinIO分布式存储服务

一、前言 最近项目中使用到了MinIO的分布式存储系统&#xff0c;记录一下Minio服务的相关概念以及使用方法。 二、基本概念 MinIO 对象存储系统是为海量数据存储、人工智能、大数据分析而设计&#xff0c;基于Apache License v2.0 开源协议的对象存储系统&#xff0c;它完全…