Linux —— 线程

我们今天进入线程的学习:

什么是线程

我们先来了解一个笼统的概念:简单来说,线程(Thread)是操作系统能够进行运算调度的最小单位

这句话其实很抽象,我们来画个图来分析一下:

首先我们知道进程
在这里插入图片描述
然后,我们可以在此基础上,如果可以让多个PCB指向一个进程空间,可以提高执行效率:
在这里插入图片描述
然后,我们称红色的为线程
在这里插入图片描述
这里我们可以得到这样几个事实(概念):

  1. 进程是资源分配的基本单位,因为只有进程会有进程地址空间,才会运行。
  2. 线程是在进程类的,是进程的小弟,帮进程做事,自己和进程共享进程地址空间。

这个时候,我们再来看看线程的概念:

线程(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用于结束一个线程;WaitForSingleObjectWaitForMultipleObjects可以用来同步线程;而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),是因为相比于传统的进程,线程在以下几方面显著地减少了资源消耗和管理开销,从而显得更为轻量:

  1. 共享资源:线程间共享同一个进程的地址空间,包括代码段、数据段以及堆空间。这意味着线程不需要为每个线程复制一份程序代码和全局变量,大大节省了内存资源。
  2. 上下文切换:线程之间的上下文切换比进程更快。因为它们共享同一地址空间,内核在切换时不需要保存和恢复整个地址空间的内容,只需要保存和恢复线程特有的上下文信息,如寄存器状态、栈指针等。
  3. 创建销毁:创建和销毁线程的代价低于进程。由于减少了资源分配的需求,线程的创建速度通常快于进程。同样,销毁线程时,只需释放它独有的资源(如栈空间),而不必回收整个地址空间。
  4. 通信成本:线程间通信更高效。由于共享内存空间,线程可以直接读写同一块内存区域来交换数据,而不需要通过复杂的进程间通信(IPC)机制,如管道、套接字等。
  5. 调度开销:线程的调度通常由同一进程内的线程库或操作系统内核直接管理,而不需要像进程那样涉及更复杂的权限检查和资源分配,因此调度成本较低。

综上所述,线程在资源使用、上下文切换、创建销毁、通信及调度等方面相比进程更加高效,故被形象地称为轻量级进程。这种轻量化使得线程成为实现并发执行、提高程序性能和响应速度的有效手段。

  • 33
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值