首页 > 运维 > linux运维 > 正文

【Linux】深入理解线程控制

爱谁谁
发布: 2025-06-26 13:12:34
原创
799人浏览过

一、线程等待的原理

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. 需要互斥的原因

    10分钟内自己学会PHP
    10分钟内自己学会PHP

    10分钟内自己学会PHP其中,第1篇为入门篇,主要包括了解PHP、PHP开发环境搭建、PHP开发基础、PHP流程控制语句、函数、字符串操作、正则表达式、PHP数组、PHP与Web页面交互、日期和时间等内容;第2篇为提高篇,主要包括MySQL数据库设计、PHP操作MySQL数据库、Cookie和Session、图形图像处理技术、文件和目录处理技术、面向对象、PDO数据库抽象层、程序调试与错误处理、A

    10分钟内自己学会PHP 524
    查看详情 10分钟内自己学会PHP

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

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

    以下是相关的代码示例:

#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,这就是我们要进行进程互斥的原因。

以上就是【Linux】深入理解线程控制的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号