Linux —— 线程
- 什么是线程
- Linux如何实现线程
- Winodws如何实现线程
- 使用一下线程
- pthread_create
- 函数原型
- 参数说明
- 返回值
- 如何解决
- ps -aL 查看线程
- 线程为什么轻量
我们今天进入线程的学习:
什么是线程
我们先来了解一个笼统的概念:简单来说,线程(Thread)是操作系统能够进行运算调度的最小单位。
这句话其实很抽象,我们来画个图来分析一下:
首先我们知道进程:
然后,我们可以在此基础上,如果可以让多个PCB指向一个进程空间,可以提高执行效率:
然后,我们称红色的为线程:
这里我们可以得到这样几个事实(概念):
- 进程是资源分配的基本单位,因为只有进程会有进程地址空间,才会运行。
- 线程是在进程类的,是进程的小弟,帮进程做事,自己和进程共享进程地址空间。
这个时候,我们再来看看线程的概念:
线程(Thread)是操作系统能够进行运算调度的最小单位,它是进程内部的一个执行序列,代表了进程中一个单一的顺序控制流。一个进程可以包含一个或多个线程,这些线程共享所属进程的内存空间和资源,比如代码段、数据段、文件句柄等,但每个线程都拥有自己的程序计数器、栈空间(调用栈)、寄存器组等执行上下文,以实现独立的执行路径。
线程的存在使得程序能够并发执行不同的任务,提高了程序的执行效率和响应速度,特别是在多核处理器系统中,多个线程可以同时在不同的CPU核心上运行,实现真正的并行处理。线程之间的协作和数据共享相对灵活,但同时也引入了资源竞争和同步问题,需要通过互斥锁、信号量等机制来解决潜在的竞态条件和死锁问题。
线程分为两类:内核线程(Kernel Thread)和用户线程(User Thread)。内核线程直接由操作系统内核管理,其创建、调度和切换等操作都是在内核层面完成的;用户线程则由应用程序管理,有时需要借助于内核线程来实现多线程的并行执行。在某些系统中,还存在混合模式,如N:1模型(多个用户线程映射到一个内核线程)或M:N模型(多个用户线程映射到多个内核线程),以平衡用户态与内核态切换的成本和线程管理的灵活性。
Linux如何实现线程
Linux在实现线程上面偷了懒,并没有专门实现,而是对照进程:
Linux 实现线程的概念确实借鉴了进程的实现方式。在Linux内核中,没有专门的“线程”这一概念,而是将线程作为轻量级进程(Lightweight Processes, LWP)来处理。这意味着每个线程在内核中都表现为一个标准的进程,拥有自己的
task_struct
,这是Linux内核中表示进程的核心结构体。然而,这些线程之间共享某些资源,如地址空间、文件描述符和信号处理等,这与传统意义上的独立进程不同。
Linux使用clone()
系统调用来创建线程,该调用允许父子进程共享某些资源,同时保持独立的执行流和少量独立的状态,如线程ID、栈等。通过精心选择clone()
的参数,可以创建具有不同资源共享级别的新执行实体,从而实现线程。通常,线程库如POSIX threads (pthreads) 会封装clone()
系统调用,为程序员提供更高层次的线程创建和管理接口。
因此,可以说Linux中的线程实现是基于进程概念的扩展,利用了进程的基础设施并加以调整,以支持在同一进程内的多个执行流,同时保持高效的资源复用。
Winodws如何实现线程
Wnidows是专门实现了线程的概念:
是的,Windows操作系统实现了线程的概念。在Windows中,线程是执行代码的基本单位,而进程则是资源分配的最小单位,每个进程可以包含一个或多个线程。Windows通过一系列线程相关的API来支持线程的创建、管理和调度,这些API允许开发者创建、终止线程,设置线程的优先级,进行线程同步,以及管理线程的执行状态。
例如,使用CreateThread
或更现代且功能更全面的CreateThreadEx
函数可以创建一个新的线程;ExitThread
用于结束一个线程;WaitForSingleObject
或WaitForMultipleObjects
可以用来同步线程;而SetThreadPriority
可以改变线程的优先级等。此外,Windows还提供了线程局部存储、互斥量、信号量、事件等机制来帮助开发者管理线程间的通信和同步问题。
总之,Windows通过其内核对象和用户模式API全面支持了线程的概念,并且在设计上确保了线程能够在多处理器系统上有效地并行执行。
使用一下线程
我们可以使用一下线程的接口:
pthread_create
pthread_create
函数是POSIX线程库(Pthreads)中的一个核心函数,用于在UNIX-like系统(如Linux、macOS等)中创建一个新的线程。这个函数允许程序实现多线程并发执行,提高执行效率和响应速度。以下是pthread_create
函数的基本信息和用法:
函数原型
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void *), void *restrict arg);
参数说明
tidp
: 指向线程标识符(pthread_t类型)的指针,用于存储新创建线程的标识。成功创建后,线程的ID将被写入此地址。attr
: 一个指向pthread_attr_t
结构的指针,该结构可以用来设置线程的属性,如栈大小、调度策略等。如果为NULL
,则使用默认属性。start_rtn
: 指向线程开始执行的函数的指针,也就是线程的入口点。这个函数必须接收一个void *
类型的参数,并返回一个void *
类型的指针。arg
: 传递给线程入口函数的参数,类型为void *
。在线程启动时,这个参数会被传递给start_rtn
函数。
返回值
- 如果成功创建线程,
pthread_create
返回0。 - 如果出现错误(比如资源不足),则返回一个非零的错误码。
#include<iostream>
#include<unistd.h>
#include<pthread.h>// 线程函数,打印线程信息
void *ThreadRountine(void* arg)
{const char *thread_name = (const char*)arg; // 将参数转换为字符串while(true) // 无限循环{std::cout << "I am a thread process" << ",pid: "<< getpid()<< std::endl; // 打印线程信息sleep(1); // 休眠1秒}
}int main()
{pthread_t tid; // 定义线程ID// 创建线程,传入线程函数和参数pthread_create(&tid,nullptr,ThreadRountine,(void*)"thread 1");while(true) // 无限循环{std::cout << "I am the main thread"<< ",pid: "<< getpid()<< std::endl; // 打印主线程信息sleep(1); // 休眠1秒}return 0;
}
我们发现运行起来了,但是很乱,这是因为主线程和创建的子线程都在同时尝试向标准输出(stdout)写入内容,而std::cout的输出默认是非原子性的,即输出操作可以被其他线程中断。当多个线程并发写入时,它们可能交错打印,导致输出混乱。
如何解决
上面的问题就是竞争,线程竞争我们在学习操作系统中,已经学习过许多方法,这里我们用锁来解决:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <mutex> // 引入互斥锁std::mutex cout_mutex; // 全局互斥锁实例void *ThreadRoutine(void *arg)
{const char *thread_name = (const char *)arg;while (true) {std::unique_lock<std::mutex> lock(cout_mutex); // 锁定互斥锁std::cout << "I am a thread process" << ", pid: " << getpid() << std::endl;lock.unlock(); // 解锁sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread 1");while (true) {std::unique_lock<std::mutex> lock(cout_mutex); // 锁定互斥锁std::cout << "I am the main thread" << ", pid: " << getpid() << std::endl;lock.unlock(); // 解锁sleep(1);}return 0;
}
ps -aL 查看线程
引入线程之后,我们用ps -ajx查不出具体的区别:
这个时候,我们要用ps -aL 查看线程
上面的LWP(Light Weight Process)就是线程,这里我们看到除了进程的13091,还有一个线程13902属于13901。
线程为什么轻量
线程之所以被称为轻量级进程(Lightweight Process),是因为相比于传统的进程,线程在以下几方面显著地减少了资源消耗和管理开销,从而显得更为轻量:
- 共享资源:线程间共享同一个进程的地址空间,包括代码段、数据段以及堆空间。这意味着线程不需要为每个线程复制一份程序代码和全局变量,大大节省了内存资源。
- 上下文切换:线程之间的上下文切换比进程更快。因为它们共享同一地址空间,内核在切换时不需要保存和恢复整个地址空间的内容,只需要保存和恢复线程特有的上下文信息,如寄存器状态、栈指针等。
- 创建销毁:创建和销毁线程的代价低于进程。由于减少了资源分配的需求,线程的创建速度通常快于进程。同样,销毁线程时,只需释放它独有的资源(如栈空间),而不必回收整个地址空间。
- 通信成本:线程间通信更高效。由于共享内存空间,线程可以直接读写同一块内存区域来交换数据,而不需要通过复杂的进程间通信(IPC)机制,如管道、套接字等。
- 调度开销:线程的调度通常由同一进程内的线程库或操作系统内核直接管理,而不需要像进程那样涉及更复杂的权限检查和资源分配,因此调度成本较低。
综上所述,线程在资源使用、上下文切换、创建销毁、通信及调度等方面相比进程更加高效,故被形象地称为轻量级进程。这种轻量化使得线程成为实现并发执行、提高程序性能和响应速度的有效手段。