linux线程控制

POSIX 线程库(简称 pthread)是 UNIX/Linux 系统下遵循 POSIX 标准 的多线程编程接口,提供了一套跨平台的线程创建、同步和管理机制。

1. 线程创建

1.1 pthread_create

pthread_create()是 POSIX 线程库中用于创建新线程的核心函数,其功能类似于进程中的fork(),但更轻量级

返回值:成功时返回 0,失败时返回错误码(非 errno,需用 strerror() 转换)。

参数解析

参数类型作用
threadpthread_t *输出参数,用于保存新线程的唯一标识符(线程ID)。
attrpthread_attr_t *线程属性(如栈大小、调度策略),传 NULL 表示使用默认属性。
start_routinevoid *(*)(void *)线程入口函数,格式必须为 void *func(void *args)
argvoid *传递给 start_routine 的参数,需强制转换为 void* 类型。

线程被创建好后,新线程要被主线程等待,类似僵尸进程的问题。

1.2 pthread_join

pthread_join 是 POSIX 线程库中用于线程同步和资源回收的关键函数,主要作用是阻塞当前线程,直到目标线程终止,并获取其返回值。

pthread_t thread,       // 目标线程的ID(需等待的线程)
    void **retval       // 输出参数:存储目标线程的返回值(可设为NULL)

    成功返回 0,失败返回错误码(非 errno)。 

    #include <iostream>
    #include <string>
    #include <cstdio>
    #include <pthread.h>
    #include <unistd.h>
    
    void showtid(pthread_t tid)
    {
        printf("tid: 0x%lx\n", tid);
    }
    void *routine(void *args)
    {
        std::string name = static_cast<const char*>(args);
        int cnt = 5;
        while(cnt--)
        {
            std::cout << "我是一个新线程:" << name << std::endl;
        }
        return nullptr;
    }
    int main()
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    
        showtid(tid);
    
        pthread_join(tid, nullptr);
        return 0;
    }
    

    1.3 pthread_self

    pthread_self()是 POSIX 线程库中用于获取当前线程唯一标识符(线程ID)的函数,其功能类似于进程中的 getpid()。

    #include <iostream>
    #include <string>
    #include <cstdio>
    #include <pthread.h>
    #include <unistd.h>
    int flag = 1000;
    void showtid(pthread_t tid)
    {
        printf("tid: 0x%lx\n", tid);
    }
    
    std::string FormatId(pthread_t id)
    {
        char tid[64];
        snprintf(tid, sizeof(tid), "0x%lx", id);
        return tid;
    }
    
    void *routine(void *args)
    {
        std::string name = static_cast<const char*>(args);
        pthread_t id = pthread_self(); 
        int cnt = 5;
        while(cnt--)
        {
            sleep(1);
            std::cout << "我是一个新线程:" << name << "我的id'是:" << FormatId(id) << std::endl;
            flag++;
        }
        return (void*)100;
    }
    int main()
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    
        showtid(tid);
    
        int cnt = 5;
        while(cnt--)
        {
            std::cout << "我是主线程:" << "我的id'是:" 
            << FormatId(pthread_self()) << ", flag: " << flag << std::endl;
            sleep(1);
        }
    
        void *ret = nullptr;
        pthread_join(tid, &ret);
        std::cout << "ret: " << (long long)ret << std::endl;
        return 0;
    }

    可以看到两个线程共享同一份资源

    两个线程都调用了这个函数,这是一个可重入函数 

    std::string FormatId(pthread_t id)
    {
        char tid[64];
        snprintf(tid, sizeof(tid), "0x%lx", id);
        return tid;
    }

    给新线程传递的参数和返回值可以是任意类型

    class Task
    {
    public:
        Task(int a, int b)
        : _a(a)
        , _b(b)
        {}
        int Execute()
        {
            return _a + _b;
        }
        ~Task(){}
    private:
        int _a;
        int _b;
    };
    class Result
    {
    public:
        Result(int result) : _result(result){}
    
        int GetResult() { return _result;}
        ~Result(){}
    private:
        int _result;
    };
    
    void *routinue(void *args)
    {
        Task *t = static_cast<Task*>(args);
        sleep(1);
        Result *res = new Result(t->Execute());
        sleep(1);
        return res;
    }
    
    int main()
    {
        pthread_t tid;
        Task *t = new Task(10, 20);
        pthread_create(&tid, nullptr, routinue, (void*)t);
    
        Result *ret = nullptr;
        pthread_join(tid, (void **)&ret);
        int n = ret->GetResult();
    
        std::cout << "进程结束,退出码:" << n << std::endl;
    
        delete t;
        delete ret;
    
        return 0;
    }

    2. 线程终止

    如果需要只终止某个线程而不终止整个进程,可以有三种方法。

    1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

    2. 线程可以调用pthread_ exit终止自己。
    参数等价于return void*

     3. ⼀个线程可以调用pthread_ cancel终止同⼀进程中的另⼀个线程。

    pthread_cancel用来取消同一个进程中的线程

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

    #include <iostream>
    #include <string>
    #include <cstdio>
    #include <pthread.h>
    #include <unistd.h>
    
    class Task
    {
    public:
        Task(int a, int b)
        : _a(a)
        , _b(b)
        {}
        int Execute()
        {
            return _a + _b;
        }
        ~Task(){}
    private:
        int _a;
        int _b;
    };
    class Result
    {
    public:
        Result(int result) : _result(result){}
    
        int GetResult() { return _result;}
        ~Result(){}
    private:
        int _result;
    };
    
    void *routinue(void *args)
    {
        Task *t = static_cast<Task*>(args);
        sleep(100);
        Result *res = new Result(t->Execute());
    
        //return res;
        pthread_exit(res); //与return res等价
    }
    
    int main()
    {
        pthread_t tid;
        Task *t = new Task(10, 20);
        pthread_create(&tid, nullptr, routinue, (void*)t);
    
        sleep(3);
        pthread_cancel(tid);
        std::cout << "新线程被取消" << std::endl;
    
        void *ret = nullptr;
        pthread_join(tid, &ret);
        std::cout << "新线程结束,退出码:" << (long long)ret << std::endl;
    
        return 0;
    }
    

    线程如果被退出,退出结果是-1

    thread 线程以不同的方式终止,通过 pthread_join 得到的终止状态是不同的,总结如下:

    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 参数。

    3.分离线程

    默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则,
    无法释放资源,从而造成系统泄漏。 如果不关心线程的返回值,join是⼀种负担,这个时候,我们
    将线程设置为分离状态,当线程退出时,自动释放线程资源。 

    pthread_detach用于将线程设置为 分离状态(detached),使得线程结束时自动释放资源,而无需调用 pthread_jion。 

    void *routinue(void *args)
    {
        pthread_detach(pthread_self());
        std::cout << "线程被分离" << std::endl;
    
        int cnt = 5;
        while(cnt--)
        {
            sleep(1);
            std::cout << "我是新进程" << std::endl;
        }
    
        return nullptr;
    }
    
    int main()
    {
        pthread_t tid;
        Task *t = new Task(10, 20);
        pthread_create(&tid, nullptr, routinue, (void*)t);
    
        int cnt = 5;
        while(cnt--)
        {
            sleep(1);
            std::cout << "我是主进程" << std::endl;
        }
    
        int n = pthread_join(tid, nullptr);
        if(n == 0)
        {
            std::cout << "pthread_join sucess: " << std::endl;
        }else
        {
            std::cout << "pthread_join fail: " << std::endl;
        }
        
        return 0;
    }
    

    这个地址就是线程在库中,对应的管理块 的虚拟地址

    4. 用户态线程(pthread)与 struct pthread(TCB)

    - struct pthread(TCB,Thread Control Block)
      - 是 pthread 库在 用户态 维护的线程管理结构,包含:

        struct pthread {
            void*         ret;         // 线程返回值(通过 return 或 pthread_exit 写入)
            void*         stack;       // 线程独立栈的地址
            size_t        stack_size;  // 栈大小
            pthread_attr_t attr;       // 线程属性(如分离状态、调度策略)
            pid_t         tid;         // 内核 LWP(通过 gettid() 获取)
            // 其他状态(如取消标志、锁等)
        };

      - pthread_create 会动态分配 struct pthread 对象,并返回其地址(即 pthread_t,本质是用户态句柄)。使用pthread_create,会在库中创建TCB,在内核中创建轻量级进程(调用系统调用clone)

    - 线程退出时
      -struct pthread 也就是TCB,有一个属性void *(ret),当线程执行return或调用 `pthread_exit()时,就会把返回值写入ret中。  
      - 此时线程虽然执行结束,但这个数据块并没有被释放,所以需要pthread_join,并且需要传入该线程的tid作为参数已找到该线程对应的数据块

    5. 内核态线程(LWP)与 task_struct(PCB)

    - clone() 系统调用 
      - pthread_create 底层通过 clone() 创建 内核线程(LWP),关键参数:

        clone(
            fn,                     // 线程函数
            stack,                  // 用户态栈地址
            CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD,
            args                    // 传递给线程的参数
        );
        - `CLONE_THREAD`:新线程共享相同的 `PID`(属于同一线程组)。  
        - `CLONE_VM`:共享地址空间(同一进程)。  

    - task_struct(PCB)
      - 内核为每个 LWP 维护一个 `task_struct`,包含:

        struct task_struct {
            pid_t          pid;      // 线程组 ID(用户态看到的 PID)
            pid_t          tgid;     // 等同于 pid(主线程的 PID)
            pid_t          tid;      // 内核 LWP(唯一线程 ID)
            struct mm_struct *mm;    // 内存管理(共享同一进程的 mm)
            // 调度相关:时间片、优先级、上下文(registers、FPU state)
        };

    用户态与内核态的协作
    - pthread_t vs LWP
      | 概念               | 用户态(pthread 库)          | 内核态(Linux 内核)          |
      |--------------------|--------------------------------------|-----------------------------------------|
      | 线程标识符      | pthread_t(TCB 地址)      | tid(LWP,通过 `gettid()` 获取) |
      | 调度单位        | 无(依赖内核 LWP)            | task_struct(LWP)         |
      | *栈管理          | 用户态分配栈(`stack` 属性) | 内核映射用户栈到进程地址空间 |

    - 线程创建流程
      1. 用户调用 pthread_create。  
      2. pthread 库分配 struct pthread(TCB)和用户栈。  
      3. 调用 clone() 创建内核线程(LWP),共享进程地址空间。  
      4. 新线程从用户态启动函数(fn)开始执行。  

    - 线程退出流程
      1. 线程调用 return 或 pthread_exit,返回值写入 TCB 的 ret。  
      2. 内核线程(LWP)退出,但 struct pthread 仍保留(供 `pthread_join` 读取)。  
      3. pthread_join 回收 TCB 和用户栈资源。  

    pthread_t 到底是什么?
    - 在 glibc 中,pthread_t 是 struct pthread(用户态 TCB 的地址)。  
    - 其他实现(如 musl libc)可能直接使用 `tid`(LWP),但 Linux 主流实现是 TCB 地址。

    总结
    - 用户态 pthread 库 管理 struct pthread(TCB),负责线程创建、属性、返回值等。  
    - 内核态 task_struct 管理 LWP,负责调度、时间片、上下文切换。  
    - pthread_t 是用户态句柄,LWP 是内核调度单位,二者通过 clone() 协作。  
    - 务必调用 `pthread_join 或 pthread_detach 避免资源泄漏!

    6.独立栈

    1. 主线程(进程)的栈
    - 分配方式:
      - 通过 `fork()` 创建时,继承父进程的栈空间地址,采用 写时拷贝(CoW)机制。
      - 栈空间可动态增长(由内核自动扩展),直至达到上限(`ulimit -s` 设置的值,默认通常 8MB)。
    - 溢出行为:
      - 访问未映射的栈地址时,内核尝试扩展栈;若超出上限则触发 段错误(SIGSEGV)。
    - 特点:
      - 向下增长:栈指针从高地址向低地址移动。
      - 唯一允许“试探性”访问未映射页而不立即报错的内存区域(直到触及硬限制)。

    2. 子线程(pthread)的栈
    - 分配方式:
      - 通过 `pthread_create()` 创建时,由 glibc 调用 `mmap` 在进程的 文件映射区(共享区)分配固定大小的栈空间。

      mem = mmap(NULL, size, PROT_READ | PROT_WRITE, 
                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

      - 默认大小通常为 8MB,可通过 `pthread_attr_setstacksize()` 自定义。
    - 溢出行为:
      - 不可动态增长:栈耗尽时直接访问非法内存,立即触发段错误。
    - 特点:
      - 固定大小:由 `mmap` 预先分配,无 CoW 机制。
      - 非严格私有:虽为线程私有,但因共享进程地址空间,其他线程可通过指针非法访问(需同步控制)。独立的栈,是指其他线程不知道该栈的地址,但是可以访问里面的内容,因为共享地址空间。

    线程独立的上下文:有独立的PCB(内核)+ TCB(用户层,pthread库内部)

    独立的栈:每个线程线程都有自己独立的栈,要么是进程的,要么是库中创建进程是mmap申请出来的

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值