目录
一、Linux 线程初相识
在 Linux 操作系统的宏大舞台上,线程扮演着举足轻重的角色,是程序高效运行的关键 “幕后英雄”。它是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际执行单元。打个比方,如果把进程看作是一个工厂,那么线程就是工厂里的各个生产线,它们在同一个工厂(进程)的资源支持下,各自负责不同的生产任务,共同推动整个工厂的运转。
线程与进程既有紧密联系又有明显区别。进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位 ,拥有独立的地址空间、文件描述符、内存等资源,就像一个独立的王国,有自己独立的一套 “设施”。而线程是进程中的一个执行流,多个线程共享所属进程的资源,包括代码段、数据段、堆内存等,就如同王国内不同的工作小组,共享王国的资源,但每个小组有自己独立的工作流程(线程有自己独立的栈空间、程序计数器和寄存器等)。从创建和销毁的开销来看,进程的创建和销毁需要操作系统进行大量的资源分配和回收工作,开销较大;而线程由于共享进程资源,创建和销毁的开销相对较小。在调度方面,进程的调度相对复杂,因为进程拥有独立资源,切换时需要保存和恢复大量的上下文信息;线程调度则相对简单,切换时只需保存和恢复少量寄存器等信息,上下文切换速度更快,这使得线程在处理并发任务时具有明显优势。
线程在提升程序性能方面作用显著。在多核处理器时代,它能够充分利用多核的并行计算能力,让不同的线程在不同的核心上同时运行,实现真正的并行处理,大大提高了程序的执行效率。例如在一个视频处理程序中,一个线程可以负责视频解码,一个线程负责图像渲染,另一个线程负责音频处理,这些线程并行工作,大大缩短了视频处理的时间。对于 I/O 密集型任务,线程也能大显身手。当一个线程进行 I/O 操作(如文件读取、网络数据传输)而处于阻塞状态时,CPU 可以切换去执行其他线程的任务,避免了 CPU 资源的闲置浪费,提高了系统资源的利用率,使得程序能够在等待 I/O 的同时继续进行其他计算工作,提升了整体的响应速度和吞吐量。
二、创建线程:pthread_create 函数解析
在 Linux 线程的世界里,pthread_create函数无疑是开启多线程编程大门的关键钥匙,熟练掌握它的使用方法,是成为优秀 Linux 开发者的必经之路。
(一)函数原型剖析
pthread_create函数的原型为:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
从这个原型可以看出,它就像是一个精心设计的 “线程制造机”,接收四个不同的 “零件”,然后 “组装” 出一个新的线程。
(二)参数详解
线程标识符指针(pthread_t *thread):这个参数就像是新线程的 “身份证号码” 存放处。当我们成功创建一个新线程时,系统会生成一个唯一的线程 ID,并将其存储在thread指向的内存单元中。在后续对线程进行管理和操作时,比如等待线程结束(pthread_join)、取消线程(pthread_cancel),都需要使用这个线程 ID 来明确操作对象。例如,我们创建了多个线程来同时处理不同的任务,在主线程中要等待某个特定线程完成任务后再继续执行,就可以通过这个线程 ID 来精准定位到该线程。
线程属性指针(const pthread_attr_t *attr):它决定了新线程的 “个性特点”。如果将其设置为NULL,则表示使用默认属性来创建线程,就像我们购买一部手机,直接使用默认设置,简单方便。但如果我们有特殊需求,就可以通过设置attr来定制线程的属性。比如线程的分离状态,可设置为PTHREAD_CREATE_DETACHED(分离状态)或PTHREAD_CREATE_JOINABLE(非分离状态)。处于分离状态的线程在结束时会自动释放资源,不需要其他线程来等待它结束;而非分离状态的线程则需要调用pthread_join函数来回收资源,否则可能会造成资源泄漏 。此外,还可以设置线程的调度策略,如SCHED_FIFO(先进先出调度策略,常用于对时间要求严格的实时任务)、SCHED_RR(轮转调度策略,适用于多个优先级相同的任务轮流执行)等,不同的调度策略会影响线程获取 CPU 时间的方式和顺序。
线程运行函数指针(void *(*start_routine) (void *)):这是线程执行任务的 “核心指令”。新创建的线程从这个函数的地址开始运行,它就像是线程的 “工作指南”,规定了线程要做的具体工作。这个函数必须是一个返回类型为void *,且接受一个void *类型参数的函数。比如我们要创建一个线程来计算一组数据的平均值,那么这个start_routine函数内部就应该包含计算平均值的逻辑代码。在多线程编程中,我们可以根据不同的任务需求,编写不同的start_routine函数,让各个线程各司其职,实现复杂的功能。
运行函数参数(void *arg):它是传递给start_routine函数的 “工作材料”。通过这个参数,我们可以向线程运行函数传递各种类型的数据,比如一个整数、一个结构体指针等。当我们需要线程处理特定的数据时,就可以将这些数据封装在一个合适的结构中,然后将结构的地址作为arg传递给线程。例如,我们要创建一个线程来处理用户的登录信息,就可以将用户的账号、密码等信息封装在一个结构体中,通过arg传递给线程函数,让线程进行验证登录的操作。这也引出了下一部分我们要详细讨论的线程传参内容。
(三)返回值含义
pthread_create函数的返回值是我们判断线程创建是否成功的重要依据。如果函数创建线程成功,返回值为0,就像一场考试顺利通过,得到了一个令人满意的成绩。但如果返回值不为0,则表示创建线程失败,此时返回值是一个错误码,不同的错误码代表不同的错误原因。例如,返回值为EAGAIN,表示系统资源不足,无法创建新的线程,就像工厂里的原材料用完了,无法生产新产品;返回值为EINVAL,表示传递给函数的参数无效,可能是线程属性设置不合理或者线程运行函数指针为空等,就像我们给机器输入了错误的指令,导致机器无法正常工作;返回值为EPERM,表示调用线程没有足够的权限创建线程,就像一个普通员工没有权限启动一项重要的生产任务。当我们得到错误返回值时,应该根据错误码进行相应的错误处理,比如打印错误信息,提示用户创建线程失败的原因,或者采取一些恢复措施,如释放已分配的部分资源等,以确保程序的稳定性和健壮性。
三、线程传参:多种方式大揭秘