Linux--线程概念+线程控制

1.什么是线程

相对于进程而言,进程是承担资源调度的实体,线程在进程内部运行,是操作系统调度的基本单位。

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列。

一切进程至少都有一个执行线程。
线程在进程内部运行,本质是在进程地址空间内运行。
在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化。
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

从上图中可以说明,cpu在调度的时候是不知道它调度的是线程还是进程的。

如果我们在创建“进程”时,只创建task_struct,并要求创建出来的task_struct和父task_struct共享进程地址空间和页表,那么创建的结果就是上面这样的。

现在创建的进程不再给你独立分配地址空间和页表,而是都指向同一块地址空间,共享同一块页表。所以这四个task_struct看到的资源都是一样的,我们后续可以通过某种方式把代码区拆分成4块,让这四个task_struct执行不同的代码区域,上述的区域(数据区,堆区,栈区)也是类似处理方式。换言之,我们后续创建的3个task_struct都各自有自己的一小份代码和数据,我们把这样的一份task_struct称之为线程。对于Linux下的进程来说,Linux其实并没有真正的对线程创建对应的数据结构。

因此:

        线程本身是在进程内部运行的,操作系统中存在大量的进程,一个进程内又存在一个或多个线程,因此线程的数量一定比进程的数量多(线程 : 进程 一定是n : 1),当线程的数量足够多的时候,很明显线程的执行粒度要比进程更细。
        对于这么多的线程我们OS需要对其做管理(先描述,再组织),在大部分的OS中,线程都有一个tcb。如果我们的系统实现的是真线程,比如说windows平台,它就要分别对进程和线程设计各自的描述的数据块(结构体),并且很多线程在一个进程内部,所以还要维护线程tcb和进程pcb之间的关系。所以这样写出的代码,其tcb和pcb两个数据结构之间的耦合度非常复杂。设计tcb和pcb的人认为这样的进程和线程在执行流层面上是不一样的。但是Linux不这样想:在概念上没有进程和线程的区分,只有一个叫做执行流。Linux的线程是用进程PCB模拟的。所以在Linux当中,其PCB和TCB是一回事!!!

Linux的线程用进程PCB模拟的好处很明显:

  1. 不用单独设计tcb了(Linux认为tcb和pcb的属性上很大部分重叠了,不需要单独设计pcb)。
  2. 不用维护tcb和pcb之间的关系了。
  3. 不用在编写任何调度算法了。

线程的优点:

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

线程的缺点:

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

一个进程内部一定存在多个执行流,问:这些执行流在CPU的角度有区别吗?

答案是没有任何区别,CPU调度的时候照样以task_struct为单位来进行调度,这里只是task_struct背后的代码和页表只是曾经的代码和页表的一小部分而已。所以CPU执行的只是一小块代码和数据,但并不妨碍CPU执行其它执行流。所以我们就可以把原本串行的所有代码而转变成并发或并行的让这些代码在同一时间点得以推进。总结如下:以前CPU看到的所有的task_struct都是一个进程,现在CPU看到的所有的task_struct都是一个执行流(线程)

Linux下进程再理解:

有了这些知识后,我们对线程的认识也发生了改变,之前的理解是:进程==操作系统所对应的代码和数据结构和进程本身所对应的代码和数据结构。

现在的理解:

现在的进程,站在内核角度上看就是:承担分配系统资源的基本实体(进程的基座属性)。所有进程最大的意义是向系统申请资源的基本单位。因此,所谓的进程并不是通过task_struct来衡量的,除了task_struct之外,一个进程还要有进程地址空间、文件、信号等等,合起来称之为一个进程。换言之,当我们创建进程时是创建一个task_struct、创建地址空间、维护页表,然后在物理内存当中开辟空间、构建映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等。

所以,在Linux系统下,Linux是没有真正的线程的,只是通过将进程通过封装,模拟出的一个线程,所以在Linux下的PCB结构体中是小于等于其他平台的PCB的概念。

线程是操作系统调度的基本单位。

Linux下没有真正意义上的线程,那么也就绝对没有真正意义上的线程相关的系统调用。这很好理解,既然在Linux中都没有真正意义上的线程了,那么自然也没有真正意义上的线程相关的系统调用了。但是Linux可以提供创建轻量级进程的接口,也就是创建进程,共享空间。

2.进程控制

原生线程库pthread

在Linux系统中,是没有原生的线程库的,因此系统为用户层提供了原生线程库pthread。

pthread库:

  • 原生线程库实际就是对轻量级进程的系统调用进行了封装,在用户层模拟实现了一套线程相关的接口。
  • 因此对于我们来讲,在Linux下学习线程实际上就是学习在用户层模拟实现的这一套接口,而并非操作系统的接口。

下面来看下线程库提供的函数接口:

创建线程:pthread_creater()

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数:pthread_t 线程地址,attr用于创建现成的属性,通常为NULL,void* 为回调函数,为创建线程的实现方法,args为传给现成的参数。

返回值:成功返回0,失败返回error

注意:Linux不能真正意义上的帮我们提供线程的接口,但是Linux有原生线程库,使用此函数必须在编译时带上 -pthread 选项。

获取线程IP:pthread_self()

没有参数

返回值:成功返回线程地址,失败返回error.

线程等待:pthread_join()
#include <pthread.h>
pthread_join(pthread_t thread,void** retval)

参数:thread 线程id名,线程返回值,通常为空。

返回值:成功返回0,错误返回error

线程终止:pthread_exit()

#include <pthread.h>
pthread_exit(void*retval);

参数:返回值

返回值:成功退出当前线程,失败返回error

线程取消:pthread_cancel()

#include <pthread.h>
pthread_cancel(pthread_t thread);

参数:被取消线程id。

返回值:成功取消线程,返回值为0,错误返回error

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二进制原码、反码、补码、移码

机器数&#xff1a;一个数在计算机中的二进制表示形式&#xff0c;称为这个数的机器数。符号位&#xff1a;机器数是带符号的&#xff0c;在计算机中用最高位作为符号位&#xff0c;0为正数&#xff0c;1为负数。真值&#xff1a;机器数由于含有符号位&#xff0c;所以机器数的…

科技驱动固定资产管理变革:RFID技术的前沿应用

在当今激烈竞争的商业环境中&#xff0c;企业固定资产管理面临挑战&#xff0c;而RFID技术正以其独特特性和功能性彻底改变资产管理方式。本文将深入探讨RFID技术在固定资产管理中的革命性作用&#xff0c;并解析其应用带来的创新和便利。 RFID技术概述&#xff1a; RFID系统作…

Vue中methods实现原理

目录 前言 回调函数中的this指向问题 vue实例访问methods methods实现原理 前言 vue实例对象为什么可以访问methods中的函数方法&#xff1f;methods的实现原理是什么&#xff1f; 回调函数中的this指向问题 在解答前言中的问题前&#xff0c;需要了解一下回调函数中的th…

[sd_scripts]之train

https://github.com/kohya-ss/sd-scripts/blob/main/docs/train_README-zh.mdhttps://github.com/kohya-ss/sd-scripts/blob/main/docs/train_README-zh.md 支持模型fine-tune&#xff0c;dreambooth&#xff0c;lora&#xff0c;textual inversion。 1.数据准备 在任意多个…

K8S 集群搭建

1、搭建清单 2台linux服务器&#xff08;一个master节点&#xff0c;一个node节点&#xff09;&#xff0c;建议搭3台&#xff08;一个master&#xff0c;两个node&#xff09; 我使用的是腾讯云&#xff0c;节点与节点使用公网IP通信 确保2台服务器都安装了docker 2、服务…

kafka单节点创建 topic 超时

1.根据之前的知道&#xff0c;安装kafka的时候改了config的server.properies文件中的listeners配置 之前这一行是没有注释掉的&#xff0c;结果创建topic的时候时钟报错连接超时 结果资料&#xff0c;发现就是因为listeners的问题 https://blog.csdn.net/weixin_42133361/art…

汽车标定技术(九)--标定常量与#pragma的趣事

目录 1. 不添加#pragma语句 2. 添加#pragma语句 3. 标定量只给flash空间&#xff0c;不给ram指定空间 4. 总结 在之前不会使用overlay机制的时候&#xff0c;我们想要做汽车标定&#xff0c;标定常量编译出来的地址一般都应该是ram的地址&#xff0c;而且在链接文件中都会指…

安装DevEco Studio时,遇到的ohpm报错(解决)

如上图所示&#xff0c;我试了网上的各种办法&#xff0c;包括官网的文档&#xff0c;运行init.bat也不行。 接下来我说一下我的办法&#xff1a; 我从官网下载安装包后&#xff0c;将bin文件添加在了环境变量里&#xff08;最终安装的位置&#xff0c;但是因为他会自己下载所…

STM32 LED编程 GPIO的初始化(标准库)

实验的电路图介绍 实验的电路图类似于开漏接法 要初始化GPIOC接口 标准库的模板 GPIO的标准库编程接口 GPIO引脚的初始化 GPIO作为片上外设 每一个片上外设使用前一定要使能时钟 为什么要使能时钟&#xff1f;时钟是啥 时钟的使能 stm32的每一个片上外设都是时序电路 时序…

【Linux网络】1分钟使用shell脚本完成DNS主从解析服务器部署(适用于centos主机)

DNS正向解析主从解析服务器脚本 1、脚本内容 主服务器脚本 #!/bin/bash ##先修改本地DNS缓存服务器 read -p "请输入主服务器ip地址&#xff1a;" masterIP sed -i /DNS/d /etc/sysconfig/network-scripts/ifcfg-ens33 echo "DNS$masterIP" >> /e…

链表面试题

&#x1f493;作者简介&#x1f44f;&#xff1a;在校大二迷茫大学生 &#x1f496;个人主页&#x1f389;&#xff1a;小李很执着 &#x1f497;系列专栏&#xff1a;Leetcode经典题 每日分享&#xff1a;其实要过那条马路并不难&#xff0c;就看谁在对面等你❣️❣️❣️ 目录…

error: C3867: “MainWindow::copyFileCallBack”: 非标准语法;请使用 ““

error: C3867: “MainWindow::copyFileCallBack”: 非标准语法&#xff1b;请使用 “&” 这时候我们按照提示将t.testFun前面加上&又会出现error C2276: “&”: 绑定成员函数表达式上的非法操作&#xff0c; 解决 在函数前面加上static声明