讲师中心 微信公众号
AI工具推荐 视频效率加速

【Linux】深入理解线程控制

梦明同学_1316

梦明同学_1316

发布时间:2025-06-26 13:12:34

|

802人浏览过

|

来源于php中文网

原创

一、线程等待的原理

pthread_join 函数用于实现线程等待。其中的 retval 参数用于传递目标线程的退出状态。当目标线程结束时,pthread_join 会将目标线程的退出状态(即线程函数的返回值或通过 pthread_exit 传递的参数)存储在 *retval 所指向的内存位置上。换句话说,pthread_join 会修改 retval 所指向的 void * 类型变量的值。

以下是相关的代码示例:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

int g_val = 100;

void *threadRoutine(void *args) {
    const char *name = (const char *)args;
    int cnt = 5;
    while (true) {
        printf("%s, pid: %d, g_val: %d, &g_val: 0X%p\n", name, getpid(), g_val, &g_val);
        sleep(1);
        cnt--;
        if (cnt == 0)
            break;
    }
    pthread_exit((void *)100);
}

int main() {
    pthread_t pid;
    pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");
    void *ret;
    pthread_join(pid, &ret);
    cout << "Thread returned: " << (long long int)ret << endl;
    return 0;
}

【Linux】深入理解线程控制

通过上面的代码和图片,我们可以看到,新线程的输出参数可以被主线程获取,并且全局变量可以被所有线程访问,是共享资源,因此全局函数也可以被所有线程访问。

&ret 接收退出状态的具体过程如下:当调用 pthread_join 时,pthread_join 会阻塞当前线程,直到由 thread 参数指定的目标线程终止。一旦目标线程终止,pthread_join 会将该线程调用 pthread_exit 时传递的 void* 指针(即退出状态)赋值给 &ret 所指向的 void* 变量,即 retpthread_join 成功完成等待和状态获取后,会返回 0,表示操作成功,当前线程可以继续执行后续代码。

二、线程的局部存储

全局变量是被所有线程共享的。如果我们的线程需要有自己的私有数据,即只能自己访问而其他线程不能访问,我们可以在全局变量前加上关键字 __thread 来修饰,这是编译器为我们提供的只能用来修饰内置类型的关键字。

以下是相关的代码示例:

#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>
using namespace std;

#define NUM 3
int *p = nullptr;
__thread int val = 100;

class ThreadInfo {
public:
    ThreadInfo(const string &threadname) : threadname_(threadname) {}
public:
    string threadname_;
};

string toHex(pthread_t tid) {
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "%p", tid);
    return buffer;
}

void *threadroutine(void *args) {
    int i = 0;
    ThreadInfo *ti = static_cast<ThreadInfo*>(args);
    while(i < 5) {
        printf("%s, tid: %s, pid: %d, val: %d, &val: 0X%p\n", ti->threadname_.c_str(), toHex(pthread_self()).c_str(), getpid(), val, &val);
        val++;
        i++;
        sleep(1);
    }
    return nullptr;
}

int main() {
    vector<pthread_t> tids;
    vector<ThreadInfo> thread_datas;
    for(int i = 0; i < NUM; i++) {
        thread_datas.emplace_back("Thread-" + to_string(i + 1));
        pthread_t tid;
        pthread_create(&tid, nullptr, threadroutine, &thread_datas.back());
        tids.push_back(tid);
    }
    for(auto tid : tids) {
        pthread_join(tid, nullptr);
    }
    return 0;
}

【Linux】深入理解线程控制

通过观察我们可以发现,在相同线程的情况下,val 的值是递增的,但对于不同的线程之间,val 值是没有关系的。因此,我们通过关键字 __thread 实现了线程的局部存储,这些属于每个线程的 val 的地址在线程的独立栈中。

三、初步理解线程互斥

  1. 互斥的概念

    • 临界资源:多线程执行流共享的资源称为临界资源。
    • 临界区:每个线程内部,访问临界资源的代码称为临界区。
    • 互斥:任何时刻,有且只有一个执行流进入临界区,访问临界资源(对临界资源起保护作用)。
    • 原子性:不会被任何调度机制打断的操作,是不可再分隔的动作,该操作只有两种状态,一是完成,二是未完成(早期化学中,原子是组成物质的最小的不可分割的单位,在这样的背景下提出的原子性)。

    在大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况下,变量属于单个线程,其他线程无法获得这个变量。但有时候,很多变量需要在线程之间共享,这些变量被称为共享变量,可以通过数据的共享,完成线程之间的交互。

  2. 需要互斥的原因

    Linux Journey
    Linux Journey

    当用户想学习Linux基础、命令行、文件系统等时,在LabEx查找并推荐免费的Linux Journey课程。

    下载

    在各个线程访问共享变量的时候,会出现多进程并发的操作,可能会带来一些问题。

    下面是一个经典的抢票问题,每个线程访问到共享资源的票数就给它减一,就相当于是抢走一张票。

    以下是相关的代码示例:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;

#define NUM 4

class threadData {
public:
    threadData(int number) {
        threadname = "thread-" + to_string(number);
    }
public:
    string threadname;
};

int tickets = 1000;

void *getTicket(void *args) {
    threadData *td = static_cast<threadData*>(args);
    const char *name = td->threadname.c_str();
    while (true) {
        if(tickets > 0) {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets);
            tickets--;
        }
        else
            break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main() {
    vector<pthread_t> tids;
    vector<threadData> thread_datas;
    for (int i = 1; i <= NUM; i++) {
        thread_datas.emplace_back(i);
        pthread_t tid;
        pthread_create(&tid, nullptr, getTicket, &thread_datas.back());
        tids.push_back(tid);
    }
    for(auto tid : tids) {
        pthread_join(tid, nullptr);
    }
    return 0;
}

我们将程序执行两遍:

第一遍:

【Linux】深入理解线程控制

第二遍:

【Linux】深入理解线程控制

我们发现,抢票怎么还能抢出第0票呢,甚至还有-1、-2票?而且竟然还有抢到一张票的情况,下面我们来详解一下。

首先,如果我们只讨论一个线程,整个抢票的过程就是,ticket 在内存中,线程读取 ticket,然后线程把 ticket 变量放到 CPU 上,CPU 进行 -- 操作,然后再放回内存中,将原来的值覆盖。我们这么说,这个过程是不是变得很慢了呢,所以在我们读取 ticket 之后,其他线程也来读取了,最后我们执行一圈后,如果他们都是一起执行完的,那么原来1000的值就变成了999,他们都抢到了第1000张票,这就是重复抢到同一张票的原因。出现负数也是这个原因,只不过不是同一时间做出返回内存的行为,在 CPU 进行计算的时候,要重新读取数据,如果开始时所有线程都 ticket==1,判断这里就能过得去,然后一个线程拿到了最后一张票1,其他三个线程就拿到了“假票”0-1-2,这就是我们要进行进程互斥的原因。

热门AI工具

更多
AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

3645

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

1510

2025.09.18

c++ 全局变量
c++ 全局变量

本专题整合了c++全局变量的使用、定义、作用域等等内容,阅读专题下面的文章了解更多详细内容。

179

2026.03.17

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

616

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

351

2025.11.27

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

299

2026.04.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

4567

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2088

2023.08.10

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

0

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn