Linux 线程

目录

一.线程的概念

1.什么是线程?

[2.Linux 系统对线程的实现](#2.Linux 系统对线程的实现)

线程比进程要更轻量化体现在什么方面??

线程切换较进程切换效率高的原因??

①cache缓存(主要原因)

②寄存器的刷新(次要原因)

3.线程的理解(原理)

[4.Linux 线程的周边概念](#4.Linux 线程的周边概念)

a.线程的优点

b.线程的缺点

c.线程的独立资源(同一进程内)

d.线程的共享资源(同一进程内)

二.线程控制

1.线程的创建

[a.pthread_create ------ 原生线程库](#a.pthread_create —— 原生线程库)

①函数详解

[② pthread 线程库的底层原理](#② pthread 线程库的底层原理)

[b.thread ------ C++线程库](#b.thread —— C++线程库)

2.线程的终止

[a. return ------ 线程自然返回](#a. return —— 线程自然返回)

[b. pthread_exit (void* retval)](#b. pthread_exit (void* retval))

[c. pthread_cancel (tid)](#c. pthread_cancel (tid))

[d. 信号终止](#d. 信号终止)

[e. 主线程退出](#e. 主线程退出)

3.线程等待

a.什么是线程等待?

b.为什么要线程等待?

c.如何进行线程等待?

[①pthread_jion ------ 原生线程库](#①pthread_jion —— 原生线程库)

[②join ------ C++11线程库](#②join —— C++11线程库)

三.多线程

1.多线程的创建

2.线程的局部存储

3.线程分离

4.线程互斥


一.线程的概念

1.什么是线程?

课本概念:线程是比进程更加轻量化的一种执行流 ,或者说,线程是在进程内部 的一种执行流

我的理解:进程承担分配系统资源的基本实体,线程 则是CPU调度的基本单位

进程和线程的联系与区别,图解如下:

2.Linux 系统对线程的实现

a.对于Linux系统,线程复用了进程的数据结构和管理算法(如:线程间切换、线程的调度、阻塞、运行、挂起)

b.在Linux系统中,线程是在进程"内部"执行的。由于任何执行流的执行都需要有资源作依托,而程序地址空间又是进程的资源分配窗口,所以可以说,线程的资源分配 ,本质就是分配地址空间范围!!

c.在Linux系统中,由于进程的整体资源被线程分配得更少了,所以线程的执行粒度要比进程更"细"

线程比进程要更轻量化体现在什么方面??

①由于线程所用到的资源更少,所以,线程的创建和释放更加轻量化

②相比于进程间切换,线程间切换更轻量化

线程切换较进程切换效率高的原因??

①cache缓存(主要原因)

线程间切换时,不需要更新cache里面的热数据。CPU访问内存上的代码是一件较为耗时的事,而根据局部性原理,CPU会将内存上所访问位置周围的多条代码一次全部拷贝到CPU的cache(硬件级的缓存,集成在CPU内部)上,从而减少CPU访问内存的次数,提高效率。而cache在缓存时是以进程为单位的,所以线程间切换不需要刷新cache,而进程间切换需要!!

②寄存器的刷新(次要原因)

相较于进程间切换,线程间切换时,刷新的CPU寄存器更少 。CPU上有特殊的寄存器指向地址空间、页表和上下文的保存,进程间切换时,这部分的寄存器会被覆盖刷新;而线程切换时,只需覆盖一些程序运行时产生临时数据的寄存器即可。换句话说,进程间切换,需要刷新CPU上的所有寄存器;而线程间切换,只需要刷新少量的、特殊的寄存器。

3.线程的理解(原理)

创建线程和创建进程的目的都是让它们各自做各自的事,二者有什么区别?

创建线程,它是在进程内部创建的,OS不会单独为线程创建地址空间、页表、文件描述符表...等资源,仅是多创建了一个task_struct结构体,线程会与主线程共享同一套地址空间等资源

创建进程,OS不仅会为其创建一个task_struct,还会为其开辟地址空间、页表等大量资源,所以说,进程是承担分配系统资源的基本实体,进程的创建要消耗更多的系统资源,它是很"重"的!!

ps -aL 查看线程的指令

LWP (Light Weight Process , 轻量级进程,即线程)

总结,什么是线程??

Linux系统,没有真正意义上的线程,它是用进程的 task_struct 和 数据结构 来模拟的线程,我们以前所学的进程,其实只是其内部只有一个执行流(主执行流)的特殊情况。其实,一个进程内部可以有多个执行流,Linux上所有的执行流统称为轻量级进程LWP ,也就是线程!!

4.Linux 线程的周边概念

a.线程的优点

①轻量化,由于线程所占据的资源更少,所以,线程的创建和销毁更加的轻量化。

②调度效率高,CPU对线程的调度要比对进程的调度更加高效(原因上文有过详细解释)。

**b.**线程的缺点

**①缺乏访问控制,**在一个进程中,由于线程会共享大部分资源,所以可能会有个别线程访问某些数据、函数会对进程整体造成影响,牵一发而动全身。

同时,编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了本不该共享的数据,而造成不良影响的可能性是很大的,且难以调试。

②同一进程内,但凡有一个线程因收到异常信号而被终止,整个进程内的所有线程都会被终止!!

c.线程的独立资源(同一进程内)

①*寄存器(就一组,存线程的上下文数据,独立的上下文能够保证线程是被独立调度的)

②*栈(保存线程在运行时形成的各种临时变量):独立的栈结构能够保证线程间运行不会出现执行流错乱问题。

③ID、errno、信号屏蔽字(block表)和调度优先级

每一个线程在运行时都要有自己独立的栈结构,因为每一个线程都会有自己的 调用链 栈结构会保存线程在运行时产生的临时变量,它存在的意义,本质就是为了支持我们在应用层管理" 整个调用链中临时变量空间的开辟和释放 "

d.线程的共享资源(同一进程内)

①*文件描述符表

②地址空间上的代码区、全局变量区、堆区、共享区等

③每种信号的处理方式(handler表)

④当前工作目录、用户id和组id
CPU调度线程时,对时间片怎么处理呢??--- 整个进程中的所有线程瓜分该进程的时间片,因为时间片也是资源。

二.线程控制

1.线程的创建

a.pthread_create ------ 原生线程库

①函数详解

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

功能:创建一个新线程,并给新线程赋予任务。

参数一:tid 是由原生线程库自动生成的线程标识符,*tid是输出型参数。

参数二:*attr 也是输出型参数,用于获取线程的属性数据

参数三:函数指针,void*()(void*),指向线程所执行的入口函数,函数参数需要我们自定义传入。

参数四:是给参数三(函数指针指向的函数参数)传递的实参。

返回值:若线程创建成功,返回0;否则返回错误码。

② pthread 线程库的底层原理

pthread_create() 库函数底层封装 的是 clone() 系统调用接口(轻量级进程的接口),而 clone() 函数会开辟一块空间给新创建的线程,充当新线程的栈!!

当调用了 pthread 库函数的可执行程序 加载到内存时,由于 pthread 是动态库,所以 pthread 库也要被加载到内存 ,并通过页表映射到地址空间共享区内!

至于线程ID是多少?栈空间多大?回调函数地址在哪?线程自己的时间片是多少?线程当前的状态?...... 等,一切都由 pthread库 维护、管理。

每一个 pthread 线程库(动态库,实质在内存上)级别的tcb(管理线程的结构体------线程控制块)的起始地址(在进程地址空间上),叫做线程的tid!!

主线程用程序地址空间里的栈,而轻量级进程创建的栈结构是在共享区内,具体来讲是在pthread库内对应线程的 tcb结构体 中。

b.thread ------ C++线程库

template <class Fn, class... Args> explicit thread (Fn&& fn, Args&&... args);

功能:创建一个新线程,并使新线程执行任务 fn,args 是函数 fn 的参数。

例如:

void increase_global (int n); 入口函数

thread new_thread = std::thread(increase_global,1000); 创建线程

注意:由于该构造函数被 expilcit 修饰,故 thread 不支持隐式类型转换。

2.线程的终止

a. return ------ 线程自然返回

当线程函数执行到return语句时,线程会自然结束其执行,并释放所占用的资源。

b. pthread_exit (void* retval)

pthread_exit() 函数是 POSIX 线程库中用于显式终止线程的函数,当线程调用 pthread_exit() 时,它会立即终止执行,并返回退出状态给主线程,该退出状态可以通过 pthread_join() 函数来获取。

c. pthread_cancel (tid)

pthread_cancel() 函数用于向目标线程发送取消请求,尝试终止该线程的执行 ,通常在主线程内调用 , 终止指定的线程。

d. 信号终止

线程也可以通过接收信号来终止执行。在Linux中,可以向线程发送信号,如SIGTERM(软件终止信号)。线程可以通过信号处理函数来响应这些信号,执行清理操作后退出。

e. 主线程退出

在Linux中,当主线程退出时,整个进程(包括所有线程)通常会随之终止。

终止线程的错误方法------exit(),任何一个线程调用exit(),都代表整个进程的退出!!

3.线程等待

a.什么是线程等待?

简单来讲,就是进行线程的资源回收,拿到线程的退出信息,以免造成资源泄露。

b.为什么要线程等待?

① 新线程被创建,主线程也要进行等待,若主线程不等待,也会造成类似僵尸进程问题。

② 为防止新线程内存泄漏,和获取新线程的退出信息,主线程要进行线程等待。

c.如何进行线程等待?

①**pthread_jion ------**原生线程库

int pthread_jion (pthread_t tid , void** retval);

功能:对目标线程进行线程等待,拿到该线程的退出信息,并对其进行资源释放。

retval是输出型参数 *retval 就是void*(*start_routine)(void*)函数(线程入口函数)的返回值

成功,返回0;失败,返回错误码。

当主线程执行到pthread_join时,若等待的目标线程仍未退出,则主线程阻塞等待!!

线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!!

②join ------ C++11线程库

void join();

功能:将目标线程变成 non_joinable 状态,使其能够被安全释放。

注意:只有当目标进程退出后,才会执行 join() 函数,若否,则会阻塞住调用 join() 的执行流。

C++11中的线程库 ------ thread,其底层封装了Linux系统和Windows系统的原生线程库,使其具有跨平台性!!

三.多线程

1.多线程的创建

在一个循环中多次调用 pthread_create() 即可创建多个线程,如上图。创建出来的所有新线程都会进入 Entry() 函数。

其中每个线程的 Entry() 函数,都在线程自身的栈结构内,多线程间不共享各自栈结构内的数据。

线程之间没有秘密可言,同一进程内每个线程的所有数据都是可以被其他线程看到的,包括线程栈结构内的数据(如用一个全局的指针),只不过我们一般不会这么做。

全局变量是被所有的线程同时看到并访问的!

2.线程的局部存储

那么,如果我们想要使某一全局变量被线程私有 呢?

--- 线程的局部存储(即每个线程独立使用该全局变量),__thread int g_val = 100; 让全局变量被 "__thread"修饰即可,即线程的局部存储。

注意:__thread 是编译选项 ,只能修饰线程的局部存储且只能修饰内置类型数据,自定义类型不能用__thread 修饰!!

__thread,线程的局部存储有啥用??--- 它允许为每个线程分配独立的存储空间,从而避免了变量共享所带来的线程安全问题,因此不需要通过锁或其他同步机制来访问这些数据,从而减少了同步开销。

3.线程分离

默认情况下,新创建的线程是 joinable(需要被等待)的,线程退出后,需要对其进行线程等待,否则无法释放资源,从而造成系统内存泄漏。

但如果我们不关心线程的返回值,线程等待就成了一种负担,这个时候,我们可以告诉系统,当线程退出时,自动回收其资源即可!
获取线程ID:pthread_t pthread_self (void); //在线程内调用,获取线程自身的ID ------ tid

int pthread_detach (pthread_t tid);

****线程分离即可由主线程来做,亦可由该进程内其他线程来做,甚至线程可以自己分离自己,线程分离操作后,被分离的线程就不能再被 pthread_join 了,否则返回错误信息。

注意:当对某一线程设置 pthread_detach 后,若主线程先于该线程退出,那么这个线程的资源就会被立即释放!

4.线程互斥

当共享资源在被多线程并发访问时,就可能会出现这么一种情况,即:一个进程正在写入数据,而另一个进程正在读取数据,从而造成数据不一致问题!!

如:多个线程同时对一个全局变量进行--/++操作时,就可能导致该全局变量最终的结果与我们所预期的大相径庭。

为啥多线程对该全局变量进行操作时,屏幕上打印出来的数据很是杂乱?

*OS对tickets--操作的步骤:

1.现将内存里的tickets读入到CPU的寄存器中

2.CPU内部进行--运算

3.将计算结果写回到内存

所以,这就可能出现这么一种情况:A线程刚进入while()循环,就被OS从CPU上撤了下来,随后OS让CPU执行下一个线程B,当线程B对count进行多次--和打印操作后,OS再次进行线程间切换,运行线程A,当CPU恢复线程A上下文数据时,count的值又变成了线程B执行前的了,这就是导致线程B对count的操作全都变成了无用功!!

如何解决??--- 对共享数据的访问,保证任何时候只有一个执行流访问------互斥------锁

至于如何用锁来实习线程互斥,且听博主下文分解~~

相关推荐
ac.char几秒前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威6 分钟前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong9 分钟前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
布鲁格若门13 分钟前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda
C-cat.20 分钟前
Linux|进程程序替换
linux·服务器·microsoft
dessler21 分钟前
云计算&虚拟化-kvm-扩缩容cpu
linux·运维·云计算
怀澈12222 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
DC_BLOG25 分钟前
Linux-Apache静态资源
linux·运维·apache
学Linux的语莫26 分钟前
Ansible Playbook剧本用法
linux·服务器·云计算·ansible
skywalk81631 小时前
树莓派2 安装raspberry os 并修改成固定ip
linux·服务器·网络·debian·树莓派·raspberry