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的操作全都变成了无用功!!

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

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

相关推荐
还是奇怪41 分钟前
Linux - 安全排查 2
linux·运维·安全
tan77º2 小时前
【Linux网络编程】Socket - UDP
linux·服务器·网络·c++·udp
czhc11400756633 小时前
Linux 76 rsync
linux·运维·python
蓝易云4 小时前
Qt框架中connect()方法的ConnectionType参数使用说明 点击改变文章字体大小
linux·前端·后端
花落已飘4 小时前
多线程 vs 异步
linux·网络·系统架构
PanZonghui5 小时前
Centos项目部署之Nginx部署项目
linux·nginx
码出钞能力5 小时前
linux内核模块的查看
linux·运维·服务器
想躺平的咸鱼干6 小时前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
星辰云-6 小时前
# Linux Centos系统硬盘分区扩容
linux·运维·centos·磁盘扩容
聽雨2376 小时前
02每日简报20250704
linux·科技·金融·生活·社交电子·娱乐·媒体