线程概述:
在 Linux 系统中,线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以拥有多个线程,这些线程共享该进程的资源(如内存空间、文件描述符等),这种共享减少了资源的复制,提高了效率。但每个线程都拥有自己独立的栈空间、寄存器和程序计数器,这使得它们能够并发执行不同的代码段
进程与线程的联系与区别:
联系:
一个进程可以被理解为由进程资源、一个主线程以及若干子线程组成的集合
- 并发执行:进程和线程都能实现并发执行,允许多个任务同时进行。
- 资源管理:进程和线程都需要操作系统进行资源管理,如内存、文件描述符等。
- 执行程序:进程和线程都是用来执行程序的,它们都需要程序代码和数据
区别:
-
资源拥有:
- 进程:拥有独立的内存空间,每个进程都有自己独立的地址空间。
- 线程:线程是进程的一部分,多个线程共享同一个进程的内存空间和资源。
-
创建开销:
- 进程:创建一个新进程需要进行大量的资源分配和初始化,开销较大。
- 线程:线程的创建和销毁开销相对较小,因为它们共享进程的资源
-
独立性:
- 进程:进程是独立的执行环境,一个进程崩溃不会直接影响到其他进程。
- 线程:线程之间存在较高的耦合度,一个线程的崩溃可能会影响到同进程的其他线程。
-
调度:
- 进程:进程是操作系统进行资源分配和调度的基本单位。
- 线程:线程是CPU调度和执行的基本单位,线程的调度由操作系统的线程调度器负责
Linux下查看线程的相关命令:
1.pidstat
是 sysstat
工具包中的一个命令,它可以报告或接收有关各个进程和线程的统计信息
安装:在 Ubuntu 系统上,可以通过以下命令安装 sysstat
工具包
sudo apt install sysstat
选项:
-t
:显示指定进程所关联的线程。-p
:指定进程 ID
使用:pidstat
命令可以用来查看特定进程及其线程的信息。
pidstat -t -p pid
2.top
命令提供了一个实时的进程监控界面。
选项:
-H
:显示线程。-p
:指定要监视的进程 ID
top -H -p pid
3.ps
命令用于显示当前运行的进程信息。
选项:
-T
:显示线程
ps -T
这将显示系统中所有线程的列表。结合其他选项,如 -p
可以指定特定的进程 ID
ps
命令结合 aux
选项,可以显示所有进程及其线程:
ps aux | grep <process_name>
线程资源:
-
共享资源:
- 线程共享进程的以下资源:
- 同一块地址空间。
- 文件描述符表。
- 信号的处理方式(如:
SIG_DFL
,SIG_IGN
或者自定义的信号处理函数)。 - 当前工作目录。
- 用户 ID 和组 ID。
-
独立资源:
- 线程拥有以下独立的资源:
- 线程栈:每个线程都有自己的栈空间,用于存储局部变量和函数调用的上下文。
- 线程 ID:每个线程都有一个唯一的标识符。
- 寄存器的值:每个线程有自己的寄存器集合,用于存储状态信息。
errno
变量:每个线程有自己的errno
值,用于错误处理。- 信号屏蔽字以及调度优先级:线程可以有自己的信号屏蔽字和调度优先级
线程的基础操作:
1.创建线程
函数描述:
pthread_create()函数在调用进程中启动一个新线程。新线程通过调用start_routine()开始执行;arg作为start_routine()的唯一参数传递
函数头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
函数参数:
thread:一个指向 pthread_t 变量的指针,该变量将被设置为新创建线程的线程标识符。
attr:指向 pthread_attr_t 结构的指针,该结构定义了新线程的属性。如果传递 NULL,则使用默认的线程属性。
start_routine:新线程启动时将调用的函数。这个函数必须有以下的函数原型:
void* function_name(void* arg);
它接受一个 void* 类型的参数,并返回一个 void* 类型的值。
arg:传递给 start_routine 函数的参数
函数返回值:
成功:返回 0。
失败:返回错误码
EAGAIN 表示资源不足,无法创建新线程
EINVAL 属性中的无效设置
EPERM 没有权限设置调度策略和attr中的参数
2.分离线程
线程类型:
线程可以被分为两种类型:可结合的(joinable)和可分离的(detached)。
可结合的线程(Joinable Threads)
- 当使用
pthread_create
创建线程时,新线程默认是可结合的(也称为可连接的)。 - 可结合的线程在结束时不会自动释放其资源,而是保持状态,等待其他线程(通常是创建它的线程)调用
pthread_join
来回收其资源。 - 调用
pthread_join
的线程可以获取结束线程的返回值。 - 如果可结合的线程结束时没有其他线程调用
pthread_join
,则它将成为一个僵尸线程,占用系统资源而不被释放。
可分离的线程(Detached Threads)
- 可分离的线程是结束时自动释放其资源,无需其他线程调用
pthread_join
。 - 可以通过调用
pthread_detach
将一个可结合的线程转换为可分离的线程。 - 可分离的线程一旦结束,其资源立即被操作系统回收,且它们的返回值不会被保存。
- 可分离的线程不能被加入(joined),尝试对它们调用
pthread_join
将失败
函数描述:
pthread_detach()
函数用于将一个线程标记为分离线程(detached thread)。分离线程在结束时不会留下任何资源需要其他线程去回收,它的资源将由操作系统自动回收,也不需要其他线程调用 pthread_join()
来回收它的资源。如果尝试对一个已经分离的线程调用 pthread_join()
,将会导致错误。
函数头文件:
#include <pthread.h>
函数原型:
int pthread_detach(pthread_t thread);
函数参数:
thread:要分离的线程的线程标识符
函数返回值:
成功:返回 0。
失败:返回错误码。
EINVAL 线程不是可结合的线程
ESRCH 找不到具有该ID的线程
3.线程等待
函数描述:
pthread_join()
函数阻塞调用它的线程,直到指定的线程结束。一旦被等待的线程结束,pthread_join()
将该线程的返回值存储在 retval
指向的位置。如果不需要线程的返回值,可以将 retval
设置为 NULL
只能对可结合的(joinable)线程调用 pthread_join()
。如果线程已经被分离(detached),则调用 pthread_join()
将失败。
一旦对线程调用了 pthread_join()
,该线程的线程标识符将变得无效。
函数头文件:
#include <pthread.h>
函数原型:
int pthread_join(pthread_t thread, void **retval);
函数参数:
thread:要等待的线程的线程标识符。
retval:一个指向 void* 类型的指针的指针,用于存储结束线程的返回值
函数返回值:
成功:返回 0。
失败:返回错误码。
EDEADLK 检测到死锁(例如,两个线程试图相互连接);或thread指定调用线程。
EINVAL 线程不是一个可接合的线程。
EINVAL 另一个线程已处于等待与该线程合并的状态
ESRCH 找不到ID为thread的线程。
4.线程退出
函数描述:
pthread_exit()
函数终止调用它的线程。如果其他线程使用 pthread_join()
等待这个线程结束,它们可以获取 retval
参数指定的返回值。如果线程是分离的(detached),则其资源将自动被操作系统回收。
函数头文件:
#include <pthread.h>
函数原型:
void pthread_exit(void *retval);
函数参数:
retval:一个 void* 类型的指针,用作线程的返回值。这个返回值可以被其他线程通过 pthread_join() 函数获取
函数返回值:
此函数不返回给调用者
结语:
无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力