Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Linux线程概念和控制

Linux线程概念和控制方式

作者:有没有没有重复的名字

这篇文章主要介绍了Linux线程概念和控制方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Linux线程概念

Linux中线程如何理解

线程<=执行流<=进程

Linux中的线程模拟进程实现(线程就是轻量级进程)

与独立的进程相比,线程创建和销毁的开销较小,因为它们共享相同的内存空间和资源。

线程是进程内的执行分支,线程的执行粒度比进程要细(只需要运行一部分代码)

我们认为线程是操作系统调度的基本单位(进程内部执行流资源)

进程:承担分配系统资源的实体(分给线程)

Linux中用进程的内核数据结构模拟线程

页表也有寄存器,帮忙找到页表 

地址空间:进程的资源窗口(进程通过地址空间看到资源)

如何理解资源分配给线程

线程分配资源本质就是分配地址空间范围

访问虚拟地址时,虚拟地址有没有调用内存:

1.查页目录时,2级页表不存在(中间10位),没用被加载到内存就发生缺页中断,二级页表和页框没有建立映射关系

2.内存管理基本单位:4kb

Linux线程周边概念

线程的优点

线程的缺点

性能损失

健壮性降低

缺乏访问控制

编程难度提高

线程异常

线程用途

Linux进程VS线程

进程是资源分配的基本单位

线程是调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据: 线程ID 一组寄存器 栈 errno 信号屏蔽字 调度优先级

进程具有独立上下文(独立运行)和栈(运行时变量数据(临时))(线程执行不会出现错乱)

进程的多个线程共享 同一地址空间,

进程和线程的关系如下图:

Linux线程控制 

线程创建

Linux中没有很明确的线程概念,有轻量级进程概念,故没有直接提供线程的系统调用接口

我们需要使用第三方的pthread库

错误检查: 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。 pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回 pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误, 建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小 

LWP是系统层次概念,用户只用知道线程id

ps -aL查看所有轻量级进程

示例代码

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;
void* threadrun(void* args)
{
    while(1)
    {
        cout<<"new thread:"<<getpid()<<endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadrun,nullptr);

    while(1)
    {
        cout<<"main thread"<<getpid()<<endl;
        sleep(1);
    }
    return 0;
}

问题分析

输出混乱的表现

根本原因

具体解释

cout << "main thread" << getpid() << endl; 不是原子操作

它实际上分为多个步骤:

在这些步骤之间,另一个线程可能插入自己的输出

在后续可以用互斥锁保护输出

线程等待 

为什么需要线程等待? 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间。

在多线程程序中,主线程最后退出是良好的编程实践,主要原因包括:

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;


int g_val = 100;
void show(const string& name)
{
    cout<<name<<"say:"<<"hello thread"<<endl;
}

void *threadrun(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);

        // int a = 10;
        // a /= 0;
        cnt--;
        if(cnt == 0) break;
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadrun,nullptr);

    // while(1)
    // {
    //     cout<<"main thread"<<getpid()<<endl;
    //     sleep(1);
    // }
    sleep(5);
    pthread_join(tid,nullptr);
    cout<<"main thread quit"<<endl;
    return 0;
}

任何变量传参都会产生临时变量

返回值的拿到要用二级指针,因为返回类型是void*

 线程终止

1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。 2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED。

3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参 数。 4

. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

return

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;


int g_val = 100;
void show(const string& name)
{
    cout<<name<<"say:"<<"hello thread"<<endl;
}

void *threadrun(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);

        // int a = 10;
        // a /= 0;
        cnt--;
        if(cnt == 0) break;
    }
    return (void*)1;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadrun,nullptr);

    // while(1)
    // {
    //     cout<<"main thread"<<getpid()<<endl;
    //     sleep(1);
    // }
    //sleep(5);
    void *retval;
    pthread_join(tid,&retval);
    //cout<<"main thread quit"<<endl;
    cout << "main thread quit ..., ret: " << (long long int)retval << endl;
    return 0;
}

 

等待默认阻塞等待,不考虑异常情况(异常是进程考虑的)任何一个线程出异常整个进程都要被干掉,无法返回

exit是终止进程的,不能用来终止线程,任何一个线程调用exit直接整个进程退出了

可以用pthread_exit退出

void *threadrun(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);

        // int a = 10;
        // a /= 0;
        cnt--;
        if(cnt == 0) break;
    }
    pthread_exit((void*)100);
}

线程取消 

#include<iostream>
#include<unistd.h>
#include<pthread.h>

using namespace std;


int g_val = 100;
void show(const string& name)
{
    cout<<name<<"say:"<<"hello thread"<<endl;
}

void *threadrun(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);

        // int a = 10;
        // a /= 0;
        cnt--;
        if(cnt == 0) break;
    }
    //pthread_exit((void*)100);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,threadrun,nullptr);
    sleep(1);
    pthread_cancel(tid);
    return 0;
}

 

线程的参数和返回值不仅可以传递一般参数,也可以传递对象

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>

using namespace std;

class ThreadCalculator {
public:
    // 请求结构体
    struct Request {
        int start;
        int end;
        string threadname;
        
        Request(int s, int e, const string &name) 
            : start(s), end(e), threadname(name) {}
    };

    // 响应结构体
    struct Response {
        int result;
        int exitcode;
        
        Response(int r = 0, int e = 0) : result(r), exitcode(e) {}
    };

    // 静态线程函数
    static void* threadFunc(void* arg) {
        Request* req = static_cast<Request*>(arg);
        Response* resp = new Response();
        
        for(int i = req->start; i <= req->end; ++i) {
            cout << req->threadname << " is running, calculating... " << i << endl;
            resp->result += i;
            usleep(100000); // 100ms延迟
        }
        
        delete req;
        return resp;
    }

    // 启动计算
    Response calculate(int start, int end, const string& name) {
        pthread_t tid;
        Request* req = new Request(start, end, name);
        
        pthread_create(&tid, nullptr, &ThreadCalculator::threadFunc, req);
        
        void* ret;
        pthread_join(tid, &ret);
        Response* resp = static_cast<Response*>(ret);
        Response result = *resp;
        
        delete resp;
        return result;
    }
};

int main() {
    ThreadCalculator calculator;
    
    auto result = calculator.calculate(1, 100, "Worker Thread");
    
    cout << "Calculation result: " << result.result 
         << ", Exit code: " << result.exitcode << endl;
    
    return 0;
}

语言已经把系统调用封装了

// 目前,我们的原生线程,pthread库,原生线程库
// // C++11 语言本身也已经支持多线程了 vs 原生线程库
void threadrun()
{
    while(true)
    {
        cout << "I am a new thead for C++" << endl;
        sleep(1);
    }
}

int main()
{
    thread t1(threadrun);

    t1.join();

    return 0;
}

线程id 

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID 不是一回事。

前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要 一个数值来唯一表示该线程。

pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdlib>
#include <unistd.h>

using namespace std;

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

void *threadRoutine(void *args)
{
    while(true)
    {
        cout << "thread id: " << toHex(pthread_self()) << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");

    cout << "main thread create thead done, new thread id : " << toHex(tid) << endl;
    pthread_join(tid, nullptr);

    return 0;
}

 线程的概念是库给我们维护的,注定了线程库要维护多个线程属性集合,线程库要管理这些线程

原始线程库要加载到内存中

每一个线程的库级别的tcb的其实地址叫做线程的tid

除了主线程,其他线程的独立栈都在共享区,具体来说是pthread库中,tid指向的用户tcb中

tid指向的那一块就是tcb

每个线程是个执行流,为了不让受干扰,每个执行流要有自己的栈结构

多线程

__thread编译选项

只能定义内置类型,可以在线程启动前获得想要的属性,在线程内直接获取即可,不用再调系统调用了

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <string>

using namespace std;

// 使用__thread修饰的线程局部变量
__thread int g_val = 100;

void* threadRoutine(void* args) {
    int thread_num = *(int*)args;
    
    // 每个线程有自己的g_val副本
    g_val += thread_num;
    
    cout << "Thread " << thread_num << ": g_val = " << g_val 
         << ", address: " << &g_val << endl;
    
    return nullptr;
}

int main() {
    vector<pthread_t> tids;
    const int NUM = 3;
    
    for (int i = 0; i < NUM; i++) {
        pthread_t tid;
        int* arg = new int(i);
        pthread_create(&tid, nullptr, threadRoutine, arg);
        tids.push_back(tid);
    }
    
    for (auto tid : tids) {
        pthread_join(tid, nullptr);
    }
    
    cout << "Main thread g_val: " << g_val << endl;
    return 0;
}

全局变量是共享的:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <string>

using namespace std;

// 全局变量,所有线程共享
int g_val = 100;

void* threadRoutine(void* args) {
    int thread_num = *(int*)args;
    
    // 每个线程都会修改同一个全局变量
    g_val += thread_num;
    
    cout << "Thread " << thread_num << ": g_val = " << g_val 
         << ", address: " << &g_val << endl;
    sleep(1);
    return nullptr;
}

int main() {
    vector<pthread_t> tids;
    const int NUM = 3;
    
    for (int i = 0; i < NUM; i++) {
        pthread_t tid;
        int* arg = new int(i);
        pthread_create(&tid, nullptr, threadRoutine, arg);
        tids.push_back(tid);
        sleep(1);
    }
    
    for (auto tid : tids) {
        pthread_join(tid, nullptr);
    }
    
    cout << "Final g_val: " << g_val << endl;
    return 0;
}

 

 每个线程有自己的栈空间

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>

using namespace std;

// 全局变量,用于存储线程栈变量的地址
vector<void*> g_stack_addresses;

void* threadRoutine(void* args) {
    int thread_num = *(int*)args;
    
    // 局部变量(栈变量)
    int stack_var = thread_num * 100;
    
    cout << "Thread " << thread_num << ": stack_var address = " 
         << &stack_var << ", value = " << stack_var << endl;
    
    // 保存栈变量地址到全局vector(仅用于演示,实际开发中要小心)
    g_stack_addresses.push_back(&stack_var);
    
    // 模拟线程工作
    sleep(1);
    
    // 注意:这里stack_var的地址在函数返回后将无效
    // 只是演示每个线程有自己的栈空间
    
    return nullptr;
}

int main() {
    const int NUM = 3;
    vector<pthread_t> tids;
    
    // 主线程的栈变量
    int main_stack_var = 999;
    cout << "Main thread: stack_var address = " 
         << &main_stack_var << endl;
    
    // 创建子线程
    for (int i = 0; i < NUM; i++) {
        pthread_t tid;
        int* arg = new int(i); // 动态分配,避免栈地址共享问题
        pthread_create(&tid, nullptr, threadRoutine, arg);
        tids.push_back(tid);
    }
    
    // 等待所有线程结束
    for (auto tid : tids) {
        pthread_join(tid, nullptr);
    }
    
    // 打印所有线程的栈变量地址
    cout << "\nAll stack variable addresses:" << endl;
    for (size_t i = 0; i < g_stack_addresses.size(); i++) {
        cout << "Thread " << i << " stack_var @ " << g_stack_addresses[i] << endl;
    }
    
    return 0;
}

主线程和每个子线程数据地址不同 

但是栈与栈之间没有秘密

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <string>

using namespace std;

// 全局指针,用于演示栈共享的危险性
int* shared_ptr = nullptr;

void* threadRoutine(void* args) {
    int thread_num = *(int*)args;
    int stack_var = thread_num * 10; // 栈变量
    
    if (thread_num == 1) {
        shared_ptr = &stack_var; // 线程1将自己的栈变量地址暴露出去
    }
    
    sleep(1); // 确保线程1先运行
    
    if (thread_num == 2) {
        // 线程2尝试访问线程1的栈变量(危险!)
        if (shared_ptr) {
            cout << "Thread 2 accessing Thread 1's stack: " << *shared_ptr << endl;
        }
    }
    
    return nullptr;
}

int main() {
    pthread_t t1, t2;
    int num1 = 1, num2 = 2;
    
    pthread_create(&t1, nullptr, threadRoutine, &num1);
    pthread_create(&t2, nullptr, threadRoutine, &num2);
    
    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    
    return 0;
}

线程分离 

线程的分离可以由主线程来做也可以由线程自己分离

共享一部分资源但不是“一家人”了,出了问题由系统自动回收

示例代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>

using namespace std;

#define NUM 3

// int *p = NULL;

// __thread int g_val = 100;
__thread unsigned int number = 0;
__thread int pid = 0;

struct threadData
{
    string threadname;
};

// __thread threadData td;

string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

void InitThreadData(threadData *td, int number)
{
    td->threadname = "thread-" + to_string(number); // thread-0
}

// 所有的线程,执行的都是这个函数?
void *threadRoutine(void *args)
{
    pthread_detach(pthread_self());

    // int test_i = 0;
    threadData *td = static_cast<threadData *>(args);
    // if(td->threadname == "thread-2") p = &test_i;
    string tid = toHex(pthread_self());
    int pid = getpid();

    int i = 0;
    while (i < 10)
    {
        cout << "tid: " << tid << ", pid: " << pid << endl;

        // cout << "pid: " << getpid() << ", tid : "
        //     << toHex(number) << ", threadname: " << td->threadname
        //         << ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;
        sleep(1);
        i++;
    }

    delete td;
    return nullptr;
}

int main()
{
    // 创建多线程!
    vector<pthread_t> tids;
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData;
        InitThreadData(td, i);

        pthread_create(&tid, nullptr, threadRoutine, td);
        tids.push_back(tid);
        //sleep(1);
    }
    sleep(1); // 确保复制成功

    // for(auto i : tids)
    // {
    //     pthread_detach(i);
    // }
    // cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;

    for (int i = 0; i < tids.size(); i++)
    {
        int n = pthread_join(tids[i], nullptr);
        printf("n = %d, who = 0x%lx, why: %s\n", n, tids[i], strerror(n));
    }

    return 0;
}

关键特性

特性描述
自动资源回收分离线程终止后系统自动回收其资源(线程ID、栈等)
不可连接(join)分离后不能再使用pthread_join等待该线程
立即生效性若线程已终止,分离操作会立即触发资源回收

使用场景

注意事项

分离 vs 非分离线程对比

特性分离线程非分离线程(默认)
资源回收自动需手动pthread_join
可连接性不可join可join
线程返回值无法获取可通过pthread_join获取
僵尸线程风险未join时会产生

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

阅读全文