【Linux线程】Linux线程编程基础:概念、创建与管理

📝个人主页🌹:Eternity._

⏩收录专栏⏪:Linux " 登神长阶 "

🌹🌹期待您的关注 🌹🌹


❀Linux多线程


🔍前言:在Linux操作系统的广阔天地里,线程作为并发编程的基本单位,扮演着举足轻重的角色。它们如同操作系统的微观脉络,穿梭于各个任务之间,高效地协调着系统的运行。然而,要想驾驭好这些微观世界的精灵,对线程控制的深入理解是不可或缺的

我们深知,在多线程编程的复杂环境中,如何有效地管理线程,是确保程序稳定性和性能的关键所在。因此,本文将深入剖析Linux线程控制的核心概念,从线程的创建与终止我们将一一为您揭开它们的神秘面纱
我们力求做到理论与实践相结合。我们不仅会详细介绍线程控制的相关理论知识,还会通过丰富的实战案例,让您在动手实践中加深对线程控制技术的理解和掌握。我们相信,通过本文的学习,您将能够更加自信地面对多线程编程中的挑战,编写出更加高效、稳定的程序

让我们携手共进,共同探索Linux多线程编程的无限魅力吧!


📒1. 线程概念

线程是进程中的一个执行单元,或者说是进程内的一条执行路径、一个执行流。它是被系统独立调度和分派的基本单位,负责执行进程中的代码。每个线程都有自己独立的线程ID、程序计数器、寄存器集合以及栈空间,但它们共享同一个进程的地址空间和其他资源,如全局变量、静态变量、堆内存等

让不同PCB指向同一块地址空间,共享进程的资源,线程在进程的地址空间中运行,线程就是一种类似与进程的轻量级进程,但是线程是一个没有独立的地址空间的PCB结构,线程切换效率高

注意:线程是CPU调度的基本单位,进程是承担系统调用的基本实体


  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流


Cache:

Cache是位于CPU和主存储器(DRAM)之间的一块高速缓冲存储器,规模较小但速度非常快,通常由SRAM(静态存储器)组成。其主要功能是暂时存储CPU即将访问的数据,从而提高CPU数据输入输出的速率。当CPU需要访问数据时,会首先在Cache中查找,如果找到所需数据,则直接从Cache中读取,避免了访问速度较慢的主存,从而提高了系统的整体性能

线程切换效率高的原因:

  • 寄存量少
  • 不需要重新更新Cache

线程的优点:

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点:

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变
  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的
  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
  • 编程难度提高
    编写与调试一个多线程程序比单线程程序困难得多

线程异常:

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

线程用途:

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验

📜2. 进程VS线程

进程是资源分配的基本单位,线程是调度的基本单位,线程共享进程数据,但也拥有自己的一部分数据

  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级
    进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到

除了上面,各线程还会共享进程的其他资源:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

📚3. 线程控制

POSIX线程库定义了一套用于创建、操纵和管理线程的API。这些API允许程序员在Unix-like系统(如Linux、Solaris)上编写多线程程序

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的
  • 要使用这些函数库,要通过引入头文件 <pthread.h>
  • 链接这些线程函数库时要使用编译器命令的"-lpthread"选项

如何查看线程:

指令:ps -aL


🌊创建线程

pthread_create:

cpp 复制代码
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

参数说明:

  • thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传递给线程函数的参数,可以是任何类型的数据,但需要通过类型转换与线程函数中的参数类型匹配,也可以传一个类

返回值:成功返回0;失败返回错误码

代码示例:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <pthread.h>

void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);
    while(1)
    {
        cout << "new thread" << ", thread name: " << threadname << endl;
        sleep(1);
    }
    
}

int main()
{
    pthread_t tid;

    ThreadData *td = new ThreadData("thread_1", (uint64_t)time(nullptr), Print);

    pthread_create(&tid, nullptr, ThreadRountine, td);
    
    while(1)
    {
        cout << "I am a main thread" << endl;
        sleep(1);
    }
    return 0;
}

🍂线程终止

在多线程编程中,线程终止(Thread Termination)是指一个线程结束其执行过程,释放相关资源,并退出其生命周期。线程终止可以是由于线程正常完成其任务,也可以是由于某些异常情况或外部请求导致的提前结束

只终止某个线程而不终止整个进程:

  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
  • 线程可以调用pthread_ exit终止自己
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

pthread_exit:

代码示例:(运行5秒后退出)

cpp 复制代码
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }

    // return nullptr;
    pthread_exit(nullptr);
}

pthread_cancel:

cpp 复制代码
int pthread_cancel(pthread_t thread);

参数:

  • thread:线程ID

代码示例:

cpp 复制代码
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }

    cout << "other thread cancel done" << endl;
    
    return nullptr;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");

    sleep(5);
    // pthread_detach(tid);
    int n = pthread_cancel(tid);
    cout << "main thread done" << ", n: " << n << endl;

    void *ret = nullptr;

    n = pthread_join(tid, &ret);
    cout << "main thread join done" << " n: " << n << ", thread return: " << (int64_t)ret << endl;
    
    return 0;
}

线程如果是被分离的,该线程可以被取消,但是不能被等待


🌸线程等待

在Linux或多线程编程环境中,线程等待通常指的是一个线程暂停其执行,直到满足某个特定条件或另一个线程完成某个任务后再继续执行

pthread_join:

cpp 复制代码
int pthread_join(pthread_t thread, void **value_ptr);

参数:

  • thread:线程ID
  • value_ptr:它指向一个指针,后者指向线程的返回值

代码示例:

cpp 复制代码
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }

    cout << "other thread done" << endl;
    // return nullptr;
    pthread_exit(nullptr);
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");

    sleep(10);
    int n = pthread_join(tid, nullptr);
    cout << "main thread done" << " n: " << n << endl;

    sleep(5);

    return 0;

}

如果我们想获取线程的返回值,我们要注意pthread_join的第二个参数是void **类型,而ThreadRoutine的返回值为void *,因此我们要对格外注意

获取线程的返回值:

cpp 复制代码
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }

    cout << "other thread done" << endl;
    // return nullptr;
    return (void *)"thread_1 done";
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");
	
	void *ret = nullptr;	

    int n = pthread_join(tid, &ret); // 注意这里要&ret	
    cout << "main thread done" << " n: " << n << "thread done and return: "<< (const char*)ret << endl;

    return 0;

}

🌵线程分离

分离线程是多线程编程中的一个重要概念,它指的是将一个线程从主线程或创建它的线程中分离出来,使其能够独立运行,并且不再需要其他线程使用特定的函数(如pthread_join())来等待其结束

pthread_detach:

cpp 复制代码
int pthread_detach(pthread_t thread);

参数:

  • thread 是你想要分离的线程的标识符(线程ID)

返回值:如果成功,pthread_detach 返回 0。如果失败,它返回一个错误码


代码示例:(在线程分离后等待线程)

cpp 复制代码
// 线程分离
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    int cnt = 5;
    while(cnt--)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }

    cout << "other thread done" << endl;
    
    return nullptr;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");

    sleep(1);
    pthread_detach(tid);

    int n = pthread_join(tid, nullptr);
    cout << "main thread done" << " n: " << n << endl;
    
    return 0;
}

在线程分离后等待线程,线程会直接返回一个错误码


🍁线程ID

线程ID本质是一个地址

cpp 复制代码
void *ThreadRoutine(void *args)
{
    string threadname = static_cast<const char*>(args);

    while(1)
    {
        cout << "new thread -> name: " << threadname << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");

    cout << "tid: " << tid << endl;
    
    while(1)
    {
        cout << "I am a main thread" << endl;
        sleep(1);
    }
    return 0;

}

pthread_self:

功能:可以获得线程自身的ID

cpp 复制代码
pthread_t pthread_self(void);

返回值:pthread_self 函数返回一个类型为 pthread_t 的值,这个值唯一地标识了调用它的线程。pthread_t 通常是一个整数或结构体,用于表示线程标识符

代码示例:

cpp 复制代码
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread_1");

    cout << "pthread_t id: " << pthread_self() << endl;

    return 0;
}


线程库要想管理线程,那么它必须要先被加载到地址空间中的mmap区域,线程库是共享的,内部要管理整个系统的,多个用户启动的所有线程

对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址,为了方便我们的库直接找到内存,而pthread_t ID就是pthread的首地址,pthread库要自己维护线程这块栈区

  • 线程可以通过函数fork来创建子进程,但是线程不能进行进程程序替换,因为线程是共用主线程的资源,一旦一个线程进行进程程序替换后,所有的线程包括主线程的代码都会被替换为别的程序

📖4. 总结

在探索Linux线程控制的旅程中,我们不仅解锁了并发编程的强大潜力,还深刻理解了线程作为操作系统调度基本单位的核心价值。从线程的创建与终止巧妙运用,再到线程属性的精细调整,每一步都充满了挑战与收获

通过本文的引导,希望每位读者都能掌握一套系统化的线程控制方法论,无论是对于初学者踏入并发编程的大门,还是对于经验丰富的开发者深化对Linux线程机制的理解,都能有所裨益。记住,技术虽不断进步,但对基础概念的深刻理解永远是创新与优化的基石
未来,随着多核处理器架构的普及和云计算、大数据等领域的快速发展,Linux线程控制的重要性将愈发凸显。它不仅是构建高性能应用的必备技能,更是深入理解现代操作系统内部机制的关键一环。让我们带着这份知识与经验,继续在技术的海洋中航行,不断探索、实践与创新,共同迎接更加辉煌的技术未来

愿每位程序员都能在并发编程的世界里,编织出属于自己的精彩篇章!

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
Linux运维技术栈8 分钟前
Ansible(自动化运维)环境搭建及ansible-vault加密配置
运维·自动化·ansible
乔巴不是狸猫12 分钟前
第11周作业
linux
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
Web阿成1 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
雷神乐乐1 小时前
Spring学习(一)——Sping-XML
java·学习·spring
李雨非-19期-河北工职大1 小时前
思考: 与人交际
学习
Bessssss1 小时前
centos权限大集合,覆盖多种权限类型,解惑权限后有“. + t s”问题!
linux·运维·centos
哦哦~9211 小时前
深度学习驱动的油气开发技术与应用
大数据·人工智能·深度学习·学习
苹果醋32 小时前
Golang的文件加密工具
运维·vue.js·spring boot·nginx·课程设计