linux笔记归纳11:线程概念与控制

线程概念与控制

目录

线程概念与控制

一、Linux线程的概念

1.1.线程的概念

1.2.线程的结论

二、分页式存储管理

2.1.物理内存管理

2.2.虚拟地址和页表

2.4.TLB缓冲器

2.5.缺页异常

三、线程的优缺点

3.1.线程的优点

3.2.线程的缺点

3.3.线程的异常

3.4.线程的用途

[四、Linux进程 VS 线程](#四、Linux进程 VS 线程)

4.1.进程和线程

4.2.进程的多个线程共享

五、Linux线程控制

5.1.pthread_create函数

[5.2.ps -aL命令](#5.2.ps -aL命令)

5.3.pthread库

5.4.pthread_join函数

5.5.pthread_self函数

5.6.pthread_exit函数

5.7.pthread_cancel函数

5.8.pthread_detach函数

5.9.多线程的安全问题

六、线程ID及地址空间布局

6.1.线程的地址空间布局

6.2.线程TCB

6.3.线程ID与线程管理块

6.4.线程源码(glibc-2.4)

七、线程封装

7.1.线程栈

7.2.线程封装的设计

八、线程的局部存储

8.1.__thread修饰

8.2.pthread_get/setname_np函数


一、Linux线程的概念

1.1.线程的概念

教材角度:

  • 进程:内核数据结构+代码和数据(执行流)
  • 线程:进程内部的一个执行分支(执行流)

内核和资源角度:

  • 进程:承担分配系统资源的基本实体
  • 线程:CPU调度的基本单位

创建一个进程时需要占用内存资源和CPU资源

  • 创建PCB、虚拟地址空间、页表
  • 加载对应进程的代码数据、动静态库,构建映射关系
  • 创建打开的文件描述符表,记录打开的文件
  • 信号的识别和处理

进程通过虚拟地址空间访问的大部分资源

虚拟地址空间是进程看见系统资源的窗口

不同的进程拥有不同的虚拟地址空间,它们看见的资源窗口也不同

创建一个能够共享同一窗口的进程让多个进程指向同一个地址空间

并将资源分配给不同的PCB,那么模拟出来的执行单元就称为线程

**资源分配的本质:**划分虚拟地址空间(页表)

1.2.线程的结论

  • 结论一:Linux中的线程可以采用进程来模拟
  • 结论二:对资源划分的本质是对虚拟地址空间范围的划分,虚拟地址就是资源的代表
  • 结论三:不同的线程执行ELF程序中不同的函数

代码由函数构成,函数的本质是代码块,每个函数都有一个入口地址

函数中每行代码都有地址,函数是虚拟地址(逻辑地址)空间的集合

所以只要让不同的线程执行ELF程序中不同的函数即可

  • 结论四:之前的进程是内部只有一个线程的进程,称为单进程
  • 结论五:Linux线程就是轻量级进程,是用轻量级进程模拟实现的

在Linux操作系统的视角下:执行流(执行流 <= 进程)

在硬件CPU的视角下:轻量级进程

  • 结论六:对于线程的实现,不同的操作系统差别较大

在windows系统中,线程在内核中也需要管理,先描述,再组织,该描述结构体叫做TCB

将一个进程的所有线程TCB,用一种数据结构组织在一起,存放在该进程的PCB中(复杂)

在Linux系统中,线程是复用PCB,用进程来模拟线程,进程的内核代码全部复

调度的数据结构与算法不用改变,调度线程与调度进程没有区别(健壮)

  • 结论七:线程在进程地址空间内运行

在现实生活中,社会上分配资源的基本实体是家庭

在家庭的内部有家庭成员,每一个人都在做不同事

当每个人把自己的事情做好,就可以过上好的生活

家庭成员就是线程,家庭成员做事的地方就是CPU

  • 结论八:
  • 进程强调独占,部分共享(比如:进程间通信)
  • 线程强调共享,部分独占(比如:函数代码块)

二、分页式存储管理

2.1.物理内存管理

磁盘中以4KB为单位进行数据块划分,CPU的CR3寄存器保存页表的首地址

可执行程序就是文件,在磁盘中存储,可执行程序存储的时候以4KB为单位

物理内存被操作系统划分成4KB的内存块,以4KB为单位与磁盘进行IO交换

物理内存中的4KB内存块称为页框或页帧,4GB的空间就有1048576个页框

4KB是操作系统划分的,不管是文件系统(磁盘),还是内存

操作系统需要管理页框 ,先描述,再组织,该描述结构体叫struct page

**stuct page mem1048576:**对内存的管理转化为对数组的操作,每一个page都会有下标

获取每一个page的下标,就可以获取每一个页框的起始地址:数组下标 * 4KB

具体物理地址 == 起始物理地址 + 页内偏移量

  • 写时拷贝也是按4KB为单位申请内存空间
  • 共享内存也是以4KB为单位申请内存空间
  • 缺页中断也是以4KB为单位将数据从磁盘加载内存
cpp 复制代码
struct page
{
    unsigned long flags;
    union
    {
        struct
        {
            struct list_head lru;
            struct address_space *mapping;
            pgoff_t index;
            unsigned long private;
        };
        struct
        {
            union
            {
                struct list_head slab_list;
                struct
                {
                    struct page *next;
#ifdef CONFIG_64BIT
                    int pages;
                    int pobjects;
#else
                    short int pages;
                    short int pobjects;
#endif
                };
            };
            struct kmem_cache *slab_cache;
            void *freelist;
            union
            {
                void *s_mem;
                unsigned long counters;
                struct
                {
                    unsigned inuse : 16;
                    unsigned objects : 15;
                    unsigned frozen : 1;
                };
            };
        };
        //...
    };
    union
    {
        atomic_t _mapcount;
        unsigned int page_type;
        unsigned int active;
        int units;
    }
    //...
#if defined(WANT_PAGE_VIRTUAL)
        void *virtual;
#endif
    //...
}

flags:存放页的状态,每一位单独表示一种状态

_mapcount:表示在页表中有多少项指向该页

virtual:页对应的虚拟地址

操作系统中,描述型结构在全局中被定义

申请物理内存的本质:检查数组,修改page

page在内核中可以由多种数据结构进行组织

文件结构体中page是通过基数树组织起来的

2.2.虚拟地址和页表

虚拟地址(x86:32位):

【0000 0000 00】【00 0000 0000】 【0000 0000 0000】

  • 前十位:0,1023
  • 中十位:0,1023
  • 后十二位:0,4095

CPU 发出的虚拟地址经MMU拆分,以CR3保存的顶层页结构物理地址为根逐级遍历页表

完成虚拟地址到物理地址的转换

申请内存 -> 查找数组 -> 找到没有被使用的page -> 记录page下标 -> 物理页框的地址

写时拷贝、缺页中断、内存申请等都可能要重新建立新的页表和映射关系的操作

进程是由一张页目录+n张页表构建的映射体系,虚拟地址是索引,物理地址(页框)是目标

虚拟地址(低12位) + 页框地址 == 物理地址,页框大小是4KB,取值范围为0,4095

在ELF文件中,同一个页框内的地址,前二十位都是一样的,属于同一个4KB中(局部性原理)

执行流看到的资源本质是在合法的情况下拥有多少虚拟地址,虚拟地址是资源的代表

  • 页表(前20位表示的是页号,后12位表示物理页框的属性)
cpp 复制代码
//⻚表标志位
//前20位表示的是页号,后12位表示物理页框的属性
#define L_PTE_PRESENT (1 << 0)
#define L_PTE_FILE (1 << 1) /* only when !PRESENT */
#define L_PTE_YOUNG (1 << 1)
#define L_PTE_BUFFERABLE (1 << 2) /* matches PTE */
#define L_PTE_CACHEABLE (1 << 3) /* matches PTE */
#define L_PTE_USER (1 << 4)
#define L_PTE_WRITE (1 << 5)
#define L_PTE_EXEC (1 << 6)
#define L_PTE_DIRTY (1 << 7)
#define L_PTE_COHERENT (1 << 9) /* I/O coherent (xsc3) */
#define L_PTE_SHARED (1 << 10) /* shared between CPUs (v6) */
#define L_PTE_ASID (1 << 11) /* non-global (use ASID, v6) */

typedef struct { unsigned long pte; } pte_t; // ⻚表项
typedef struct { unsigned long pgd; } pgd_t; // ⻚全局⽬录项

pgd_t *pgd_alloc(struct mm_struct *mm)
{
    pgd_t *ret, *init;
    ret = (pgd_t *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
    init = pgd_offset(&init_mm, 0UL);
    if (ret)
    {
#ifdef CONFIG_ALPHA_LARGE_VMALLOC
        memcpy(ret + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD,
               (PTRS_PER_PGD - USER_PTRS_PER_PGD - 1) * sizeof(pgd_t));
#else
        pgd_val(ret[PTRS_PER_PGD - 2]) = pgd_val(init[PTRS_PER_PGD - 2]);
#endif
        /* The last PGD entry is the VPTB self-map. */
        pgd_val(ret[PTRS_PER_PGD - 1]) = pte_val(mk_pte(virt_to_page(ret), PAGE_KERNEL));
    }
    return ret;
}

pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address)
{
    pte_t *pte = (pte_t *)__get_free_page(GFP_KERNEL | __GFP_REPEAT | __GFP_ZERO);
    return pte;
}

struct mm_struct
{
    struct vm_area_struct *mmap; /* list of VMAs */
    struct rb_root mm_rb;
    struct vm_area_struct *mmap_cache; /* last find_vma result */
    unsigned long (*get_unmapped_area)(struct file *filp,
                                       unsigned long addr, unsigned long len,
                                       unsigned long pgoff, unsigned long flags);
    void (*unmap_area)(struct mm_struct *mm, unsigned long addr);
    unsigned long mmap_base;        /* base of mmap area */
    unsigned long task_size;        /* size of task vm space */
    unsigned long cached_hole_size; /* if non-zero, the largest hole
    below free_area_cache */
    unsigned long free_area_cache;  /* first hole of size
     cached_hole_size or larger */
    pgd_t *pgd;                     // ⻚⽬录起始地址
}
  • **虚拟地址空间(mm_struct + vm_area_struct)本质:**进行资源的统计数据和整体数据
  • **页表的本质:**一张虚拟到物理的地图
  • **资源划分的本质:**虚拟地址空间的划分
  • **资源共享的本质:**虚拟地址空间的共享
  • **线程进行资源划分的本质:**划分虚拟地址空间(页表),获得一定范围的合法虚拟地址
  • **线程进行资源共享的本质:**对虚拟地址空间(页表条目)的共享

2.4.TLB缓冲器

多级页表虽然减少了连续存储要求和存储空间,但是降低了查询效率

所以MMU引入了TLB(Translation Lookaside Buffer)缓冲器(转译后备缓冲区)

CPU给MMU传入新的虚拟地址后,MMU先去问TLB,如果TLB有物理地址

就将拿到的物理地址发到总线给内存,但TLB容量小,如果发生TLB Miss(未命中)

MMU就去查询页表,将地址发送到总线传给内存后,将这条映射关系给TLB刷新

2.5.缺页异常

CPU给MMU虚拟地址后,在TLB和页表中都没有找到对应的物理页

会触发硬件中断,由软件逻辑来纠正错误(缺页异常 Page Fault)

new/malloc的本质:系统调用函数brk或者mmap(文件)

申请的是虚拟地址,等访问的时候触发缺页中断

,延迟申请,进而变相地提高内存使用的充分度

写时拷贝:父子进程指向同一个页框,权限设置为只读

子进程或父进程修改页框中的数据,就会对整个页框写时拷贝

申请内存:申请地址空间,移动start,end计数器的位置

如何区分缺页和越界

越界了程序不一定会崩溃,因为操作系统可能不知道

需要程序员自己来debug,因为该空间可能是合法的

页号合法性检查:

操作系统在处理中断或异常时,首先检查触发事件的虚拟地址页号是否合法

如果页号合法但页面不在内存,则缺页中断,如果页号非法,则为越界访问

内存映射检查:

操作系统检查触发事件的虚拟地址是否在内存映射范围内

如果在映射范围但不在内存,则缺页中断,如果地址不在映射范围,则越界

三、线程的优缺点

3.1.线程的优点

创建一个新线程的代价比创建一个新进程小得多,线程占用的资源比进程少

等待慢速I/O操作的同时,程序可以执行其他的计算任务,能充分利用多处理器的可并行数量

  • 计算密集型应用:为了能在多处理器系统上运行。将计算分解到多个线程中实现

在计算密集型中,线程并不是越多越好,因为线程的调度也需要时间开销

线程越多,反而浪费了计算的时间,此时线程的个数最好与核数成正相关

  • I/O密集型应用:为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作

在I/O密集型中,大部分都处于等待状态,线程越多,就可以提高工作效率

**核心点:**与进程切换相比,线程切换需要操作系统做更少的工作

进程切换:保存旧进程CPU硬件上下文,切换新进程的PCB上下文,修改CR3寄存器中的页目录

current指针用register关键字修饰,放在专用寄存器,减少内存访问,高速获取当前任务

线程切换:保存旧线程CPU硬件上下文,切换新线程的PCB上下文,不用修改页目录和页表

**主要区别一:**线程切换后虚拟地址空间是相同的,而进程切换后不同

主要区别二: 上下文的切换会扰乱处理器的缓存机制

硬件层面要对用户数据进行cache(对用户透明),而切换上下文就会

导致页表缓冲TLB(快表)和硬件Cache失效,下次运行需要重新缓存

3.2.线程的缺点

性能损失:计算密集型的线程不宜过多

健壮性降低:多线程共享虚拟地址空间,一个线程出现异常或非法操作,操作系统会杀掉整个进程

缺乏访问控制:在一个线程中调用某些OS函数会对整个进程造成影响

编程难度提高:编写与调试一个多线程程序比单线程程序困难得多

3.3.线程的异常

单线程如果出现除零,野指针问题而导致线程崩溃,进程也会随之崩溃

线程是进程的执行分支,进程终止,该进程内的所有线程也就随即推出

3.4.线程的用途

使用多线程,提高CPU密集型程序的执行效率

使用多线程,提高I/O密集型程序的用户体验

四、Linux进程 VS 线程

4.1.进程和线程

**进程:**资源分配的基本单位

**线程:**调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器(线程的CPU硬件上下文数据:线程是被独立调度的)
  • 栈(独立栈结构:线程是一个动态的概念,有生命周期)
  • errno
  • 信号屏蔽字
  • 调度优先级

4.2.进程的多个线程共享

各线程共享的进程资源与环境:

  • 虚拟地址空间
  • Text Segment、Data Segment
  • 函数
  • 全局变量
  • 文件描述符
  • 信号处理方式
  • 当前工作目录
  • 用户id和组id

五、Linux线程控制

5.1.pthread_create函数

作用:创建一个新的线程

参数1:线程ID,输出型参数

参数2:线程属性,设置为nullptr由库默认

参数3:新线程的函数名,新线程执行的函数入口

参数4:新线程的参数

返回值:

创建成功:0

创建失败:错误码

pthread头文件

第三方pthread库文件

使用第三方库编译时需要带-l选项

实验:创建一个线程

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

//一组虚拟地址表示的代码和数据
void *threadrun(void *args)
{
    std::string name = (const char*)args;
    while(true)
    {
        std::cout << "我是新线程: name: " << name << std::endl;
        sleep(1);
    } 
    return nullptr;
}

//另一组虚拟地址表示的代码和数据
int main()
{
    pthread_t tid;//线程id
    pthread_create(&tid, nullptr, threadrun, (void*)"thread-1");

    while(true)
    {
        std::cout << "我是主线程..." << std::endl;
        sleep(1);
    }

    return 0;
}

实验现象

信号的处理动作对于同一个进程的线程是共享的

5.2.ps -aL命令

LWP(Light Weight Process):轻量级进程

同一个进程中不同线程的PID相同,LWP不一样

主线程的LWP与进程的PID相同(task_struct中不仅有PID还有LWP)

CPU在调度的时候看的是LWP

调度的进程时间片等分给该进程中不同线程

任何一个线程崩溃,都会导致整个进程崩溃

多线程往显示器文件写入,显示器是共享资源,不加保护时会出现原子性错误,数据会混杂在一起

5.3.pthread库

Linux系统中不存在真正意义上的线程,所谓的线程是用轻量级进程来模拟的

所以操作系统只会提供轻量级进程的系统调用,而不会提供线程的系统调用

vfork创建的子进程与父进程共享虚拟地址空间,不会发生写时拷贝

clone创建一个轻量级进程(pthread_create的底层封装的就是clone)

但用户只认线程,所有的操作系统只会讲线程,而不是轻量级进程

在用户层与内核层中间添加软件层,用pthread库把轻量级进程封装

给用户提供一批创建线程的接口,用户就不用再关心轻量级进程

C++11引入的线程库

  • 在Linux下本质是封装了pthread库
  • 在windows下本质是封装了windows创建线程的接口

语言的跨平台可移植性:所有平台都实现一个版本,进行条件编译,形成语言层面的库

Linux环境下,大部分语言(java、pytho、php)的线程库,底层都是封装了pthread库

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

void hello()
{
    while(true)
    {
        std::cout << "新线程: hello pid: " << getpid() << std::endl;
        sleep(1);
    }
}

int main()
{
    std::thread t(hello);

    while(true)
    {
        std::cout << "我是主线程..." << ", pid: " << getpid() << std::endl;
        sleep(1);
    }

    t.join();
    return 0;
}

5.4.pthread_join函数

作用:等待新线程

参数1:线程ID

参数2:输出型参数,接收新线程函数的返回值

新线程创建好后要被主线程等待

否则会出现类似僵尸进程的问题,造成内存泄漏

5.5.pthread_self函数

作用:获得调用该函数的线程ID

sprintf:将格式化输出的字符写入字符串

snprintf:将格式化输出的字符写入规定大小的字符串

线程库是对轻量级进程做的封装,所以线程的tid不是LWP

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

int flag = 0;

void showtid(pthread_t &tid)
{
    printf("新线程tid(无符号整型): %ld\n", tid);
    printf("新线程tid(16进制): 0x%lx\n", tid);
}

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

void *routine(void *args)
{
    std::string name = static_cast<const char*>(args);
    pthread_t tid = pthread_self();
    int cnt = 5;
    while(cnt)
    {
        sleep(5);
        std::cout << "我是一个新线程,我的名字是: " << name << "我的ID是: " << FormatId(pthread_self()) << std::endl;
        cnt--;
        flag++;
    }
    return (void *)100;
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");
    (void)n;

    showtid(tid);

    int cnt = 5;
    while(cnt)
    {
        std::cout << "我是主线程,我的名字是: main thread"  << "我的ID是: " 
        << FormatId(pthread_self()) << std::endl;
        sleep(1);
        cnt--;
    }

    //ret为指针变量,也有虚拟地址空间
    void *ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "flag is : " << flag << std::endl;
    std::cout << "ret is : " << (long long int)ret << std::endl;
    return 0;
}

FormatId函数被主线程和子线程两个执行流调用,即函数被重入了

但函数内的变量是临时的,不影响全局,所以该函数是可重入函数

main函数结束代表主线程结束,一般也代表进程结束

新线程对应的入口函数运行结束,代表当前线程结束

给线程传递的参数和返回值,可以是任意的类型(对象)

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.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 *routine(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, routine, t);

    Result *ret = nullptr;
    pthread_join(tid, (void**)&ret);
    int n = ret->GetResult();
    std::cout << "新线程结束, 运行结果: " << n << std::endl;

    delete t;
    delete ret;
    return 0;
}

5.6.pthread_exit函数

作用:终止线程

线程的入口函数进行return就是线程终止

线程不能用exit终止,因为exit是终止进程的

5.7.pthread_cancel函数

作用:取消线程

做法:主线程取消新线程,取消之前新线程一定要启动

主线程和新线程的运行先后不确定

如果线程被取消,退出结果是-1【PTHREAD_CANCELED】

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.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 *routine(void *args)
{
    Task *t = static_cast<Task*>(args);
    sleep(100);
    Result *res = new Result(t->Execute());
    sleep(1);
    pthread_exit(res);
}

int main()
{
    pthread_t tid;
    Task *t = new Task(10, 20);
    pthread_create(&tid, nullptr, routine, 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;
}

5.8.pthread_detach函数

作用:分离线程

主线程不再关心新线程,而是在新线程结束的时候自己释放,需设置新线程为分离状态

如果线程被设置为分离状态,不需要进行join,此时join会失败,新线程会自动释放资源

**技术层面:**线程默认是需要被等待的(joinable状态)

如果主线程不等待新线程,新线程结束后自己退出

需要设置新线程为分离状态(!joinable or detach)

**理解层面:**主线程分离新或者新线程自己分离

被分离的新线程依旧在进程地址空间中,进程中的所有资源

被分离的线程依旧可以访问操作,主线程不需要等待新线程

实验:主线程分离新线程

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

void *routine(void *args)
{
    const char* ret = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "新线程: " << ret << std::endl;
        sleep(1);
    }
    return nullptr;
}

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

    //分离新线程
    pthread_detach(tid);
    std::cout << "新线程被分离" << std::endl;

    int cnt = 5;
    while(cnt--)
    {
        std::cout << "main线程" << std::endl;
        sleep(1);
    }

    int n = pthread_join(tid, nullptr);
    if(n != 0)
    {
        std::cout << "pthread_join error: " << strerror(n) << std::endl;
    }
    else
    {
        std::cout << "pthread_join error: " << strerror(n) << std::endl;
    }

    return 0;
}

实验:新线程自己分离

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

void *routine(void *args)
{
    //新线程自己分离
    pthread_detach(pthread_self());
    std::cout << "新线程自己分离" << std::endl;
    const char* ret = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "新线程: " << ret << std::endl;
        sleep(1);
    }
    return nullptr;
}

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

    int cnt = 5;
    while(cnt--)
    {
        std::cout << "main线程" << std::endl;
        sleep(1);
    }

    int n = pthread_join(tid, nullptr);
    if(n != 0)
    {
        std::cout << "pthread_join error: " << strerror(n) << std::endl;
    }
    else
    {
        std::cout << "pthread_join error: " << strerror(n) << std::endl;
    }

    return 0;
}

5.9.多线程的安全问题

创建多线程时,如果多线程共享同一内存空间的数据

可能会发生重入不可重入函数,产生数据不一致问题

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstring>

// 创建多线程
const int num = 10;

void *routine(void *args)
{
    sleep(1);
    std::string name = static_cast<const char *>(args);
    int cnt = 2;
    while (cnt--)
    {
        std::cout << "新线程名字: " << name << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < num; i++)
    {
        pthread_t tid;
        char id[64];
        snprintf(id, sizeof(id), "thread-%d", i);
        int n = pthread_create(&tid, nullptr, routine, id);
        if (n == 0)
        {
            // 保存所有新线程的tid
            tids.push_back(tid);
        }
        else
        {
            continue;
        }
    }
    for (int i = 0; i < num; i++)
    {
        // 主线程一个一个等待
        int n = pthread_join(tids[i], nullptr);
        if (n == 0)
        {
            std::cout << "等待新线程成功" << std::endl;
        }
    }
    return 0;
}

每一次循环都需要为新线程创建一段独立的堆空间

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstring>

// 创建多线程
const int num = 10;

void *routine(void *args)
{
    sleep(1);
    std::string name = static_cast<const char *>(args);
    delete (char *)args;

    int cnt = 2;
    while (cnt--)
    {
        std::cout << "新线程名字: " << name << std::endl;
        sleep(1);
    }
    
    return nullptr;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < num; i++)
    {
        pthread_t tid;
        char *id = new char[64];
        snprintf(id, 64, "thread-%d", i);
        int n = pthread_create(&tid, nullptr, routine, id);
        if (n == 0)
        {
            // 保存所有新线程的tid
            tids.push_back(tid);
        }
        else
        {
            continue;
        }
    }
    for (int i = 0; i < num; i++)
    {
        // 主线程一个一个等待
        int n = pthread_join(tids[i], nullptr);
        if (n == 0)
        {
            std::cout << "等待新线程成功" << std::endl;
        }
    }
    return 0;
}

六、线程ID及地址空间布局

6.1.线程的地址空间布局

Linux没有真正的线程,是用轻量级进程模拟

OS不会提供线程的接口,需要在用户层封装轻量级进程,形成原生线程库

即用户级别的库,pthread库是动态库,格式为ELF,可执行程序的文件格式也为ELF

可执行程序加载形成进程,动态链接和动态地址的重定向

要将动态库加载到内存,并且映射到当前进程的地址空间

6.2.线程TCB

进程自己的代码区可以访问到pthread库内部的函数与数据

线程也会在pthread库中维护,库的内部会存在多个创建好的线程

在用户层面,pthread库也需要对线程进行管理,先描述,再组织

(类似C标准库的fopen函数会在内存中申请FILE对象填充文件描述符和缓冲区)

6.3.线程ID与线程管理块

线程管理块包括了线程TCB、线程局部存储TLS、线程栈空间

线程ID是线程在pthread库中对应的管理块的起始虚拟地址

线程代码执行结束后,线程函数的返回值就会被拷贝到struct pthread结构体的void *ret中

但它的管理块并没有被释放,需通过线程ID进行等待,解决内存泄漏问题,并获取返回值

每个线程都必须有自己独立的栈空间

主线程用进程中虚拟地址空间中的栈

新线程用pthread库中的线程栈,属于用户空间

Linux用户级线程 : 内核LWP = 1 : 1

默认情况下,TCB中的进程状态变量joinable为1表示未分离,需要主进程等待后释放

如果设为0,则线程分离,新线程结束后,由线程库自动释放线程管理块

6.4.线程源码(glibc-2.4)

  • 线程创建
cpp 复制代码
// 线程创建
int __pthread_create_2_1(newthread, attr, start_routine, arg)
pthread_t *newthread;
const pthread_attr_t *attr;
void *(*start_routine)(void *);
void *arg;
{
    STACK_VARIABLES;

    // 1: 获取用户传入的线程属性
    const struct pthread_attr *iattr = (struct pthread_attr *)attr;
    if (iattr == NULL)
    {
        iattr = &default_attr;
    }

    // 2:定义线程库中描述线程的TCB类的指针
    struct pthread *pd = NULL;

    // 3: 用ALLOCATE_STACK宏创建TCB类的对象
    int err = ALLOCATE_STACK(iattr, &pd);
    if (__builtin_expect(err != 0, 0))
    {
        versioned_symbol return err;
    }

#ifdef TLS_TCB_AT_TP
    pd->header.self = pd;
    pd->header.tcb = pd;
#endif

    // 4: 向线程的TCB设置执行方法的地址和参数
    pd->start_routine = start_routine;
    pd->arg = arg;

    struct pthread *self = THREAD_SELF;
    pd->flags = ((iattr->flags & ~(ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)) | (self->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)));

    //线程是否分离
    pd->joinid = iattr->flags & ATTR_FLAG_DETACHSTATE ? pd : NULL;

    pd->eventbuf = self->eventbuf;
    pd->schedpolicy = self->schedpolicy;
    pd->schedparam = self->schedparam;
#ifdef THREAD_COPY_STACK_GUARD
    THREAD_COPY_STACK_GUARD(pd);
#endif
#ifdef THREAD_COPY_POINTER_GUARD
    THREAD_COPY_POINTER_GUARD(pd);
#endif

    // 参数设定
    if (attr != NULL && __builtin_expect((iattr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0, 0) && (iattr->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)) != 0)
    {
        INTERNAL_SYSCALL_DECL(scerr);
        if (iattr->flags & ATTR_FLAG_POLICY_SET)
        {
            pd->schedpolicy = iattr->schedpolicy;
        }
        else if ((pd->flags & ATTR_FLAG_POLICY_SET) == 0)
        {
            pd->schedpolicy = INTERNAL_SYSCALL(sched_getscheduler, scerr, 1, 0);
            pd->flags |= ATTR_FLAG_POLICY_SET;
        }
        if (iattr->flags & ATTR_FLAG_SCHED_SET)
        {
            memcpy(&pd->schedparam, &iattr->schedparam, sizeof(struct sched_param));
        }
        else if ((pd->flags & ATTR_FLAG_SCHED_SET) == 0)
        {
            INTERNAL_SYSCALL(sched_getparam, scerr, 2, 0, &pd->schedparam);
            pd->flags |= ATTR_FLAG_SCHED_SET;
        }
        int minprio = INTERNAL_SYSCALL(sched_get_priority_min, scerr, 1, iattr->schedpolicy);
        int maxprio = INTERNAL_SYSCALL(sched_get_priority_max, scerr, 1, iattr->schedpolicy);
        if (pd->schedparam.sched_priority < minprio || pd -> schedparam.sched_priority > maxprio)
        {
            err = EINVAL;
            goto errout;
        }
    }

    // 5: 把线程控制块的起始地址作为线程ID传递给用户
    *newthread = (pthread_t)pd;

    // 6: 检测线程属性是否分离
    bool is_detached = IS_DETACHED(pd)

    // 7: 创建线程
    err = create_thread(pd, iattr, STACK_VARIABLES_ARGS); 
    if (err != 0)
    {
        if (!is_detached)
        {
        errout:
            __deallocate_stack(pd);
        }
        return err;
    }
    return 0;
}
__pthread_create_2_1
    versioned_symbol(libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1);
  • 线程属性
cpp 复制代码
struct pthread_attr
{
    /* Scheduler parameters and priority. */
    struct sched_param schedparam;
    int schedpolicy;
    /* Various flags like detachstate, scope, etc. */
    int flags;
    /* Size of guard area. */
    size_t guardsize;
    /* Stack handling. */
    void *stackaddr;
    size_t stacksize;
    /* Affinity map. */
    cpu_set_t *cpuset;
    size_t cpusetsize;
};
  • 线程TCB
cpp 复制代码
struct pthread
{
    union
    {
#if !TLS_DTV_AT_TP
        tcbhead_t header;
#else
        struct
        {
            int multiple_threads;
        } header;
#endif
        void *__padding[16];
    };
    list_t list;

    // 线程的LWP
    pid_t tid;  
    // 线程所属进程的PID
    pid_t pid;

#ifdef __PTHREAD_MUTEX_HAVE_PREV
    __pthread_list_t robust_list;
#define ENQUEUE_MUTEX(mutex) \
    do                       \
    {                        \
        __pthread_list_t *next = THREAD_GETMEM(THREAD_SELF, robust_list.__next);

    next->__prev = &mutex->__data.__list;
    mutex->__data.__list.__next = next;
    mutex->__data.__list.__prev = &THREAD_SELF->robust_list;
    THREAD_SETMEM(THREAD_SELF, robust_list.__next, &mutex->__data.__list);

} while (0)
#define DEQUEUE_MUTEX(mutex) \
    do                       \
    {                        \
        mutex->__data.__list.__next->__prev = mutex->__data.__list.__prev;

    mutex->__data.__list.__prev->__next = mutex->__data.__list.__next;

mutex->__data.__list.__prev = NULL;
mutex->__data.__list.__next = NULL;
}
while (0)
#else
    __pthread_slist_t robust_list;
#define ENQUEUE_MUTEX(mutex)                                                          \
    do                                                                                \
    {                                                                                 \
        mutex->__data.__list.__next = THREAD_GETMEM(THREAD_SELF, robust_list.__next); \
        THREAD_SETMEM(THREAD_SELF, robust_list.__next, &mutex->__data.__list);

} while (0)
#define DEQUEUE_MUTEX(mutex) \
    do                       \
    {                        \
__pthread_slist_t *runp = THREAD_GETMEM (THREAD_SELF,
robust_list.__next);
if (runp == &mutex->__data.__list)
    THREAD_SETMEM(THREAD_SELF, robust_list.__next, runp->__next);
else
{
    while (runp->__next != &mutex->__data.__list)
        runp = runp->__next;

    runp->__next = runp->__next->__next;
    mutex->__data.__list.__next = NULL;
}
}
while (0)
#endif
    struct _pthread_cleanup_buffer *cleanup;
struct pthread_unwind_buf *cleanup_jmp_buf;
#define HAVE_CLEANUP_JMP_BUF
int cancelhandling;
#define CANCELSTATE_BIT 0
#define CANCELSTATE_BITMASK 0x01
#define CANCELTYPE_BIT 1
#define CANCELTYPE_BITMASK 0x02
#define CANCELING_BIT 2
#define CANCELING_BITMASK 0x04
#define CANCELED_BIT 3
#define CANCELED_BITMASK 0x08
#define EXITING_BIT 4
#define EXITING_BITMASK 0x10
#define TERMINATED_BIT 5
#define TERMINATED_BITMASK 0x20
#define SETXID_BIT 6
#define SETXID_BITMASK 0x40
#define CANCEL_RESTMASK 0xffffff80
#define CANCEL_ENABLED_AND_CANCELED(value) \
(((value) & (CANCELSTATE_BITMASK | CANCELED_BITMASK | EXITING_BITMASK

| CANCEL_RESTMASK | TERMINATED_BITMASK)) == CANCELED_BITMASK)
#define CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS(value) \
(((value) & (CANCELSTATE_BITMASK | CANCELTYPE_BITMASK | CANCELED_BITMASK
\
| EXITING_BITMASK | CANCEL_RESTMASK | TERMINATED_BITMASK)) \
== (CANCELTYPE_BITMASK | CANCELED_BITMASK))
struct pthread_key_data
{
    uintptr_t seq;
    void *data;
} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
bool specific_used;
bool report_events;

// 用户的栈空间
bool user_stack;

bool stopped_start;
lll_lock_t lock;
lll_lock_t setxid_futex;
#if HP_TIMING_AVAIL
hp_timing_t cpuclock_offset;
#endif
struct pthread *joinid;
#define IS_DETACHED(pd) ((pd)->joinid == (pd))
int flags;

// 线程运行完毕, 返回值就是void*, 最后的返回值就放在TCB中
// 用pthread_join获取线程退出信息的时候,就是读取该结构体
void *result;

struct sched_param schedparam;
int schedpolicy;

// 用户指定的方法和参数
void *(*start_routine)(void *);
void *arg;

td_eventbuf_t eventbuf;
struct pthread *nextevent;
#ifdef HAVE_FORCED_UNWIND
struct _Unwind_Exception exc;
#endif

// 线程自己的栈和栈的大小
void *stackblock;
size_t stackblock_size;

size_t guardsize;
size_t reported_guardsize;
struct __res_state res;
char end_padding[];
#define PTHREAD_STRUCT_END_PADDING \
    (sizeof(struct pthread) - offsetof(struct pthread, end_padding))
}
__attribute((aligned(TCB_ALIGNMENT)));
  • ALLOCREAT_STACK宏(栈空间申请)
cpp 复制代码
// 空间申请的函数(宏)
#define ALLOCATE_STACK(attr, pd) \
    allocate_stack(attr, pd, &stackaddr, &stacksize)

static int allocate_stack(const struct pthread_attr *attr, struct pthread **pdp, ALLOCATE_STACK_PARMS)
{
    struct pthread *pd;
    size_t size;
    size_t pagesize_m1 = __getpagesize() - 1;
    void *stacktop;
    assert(attr != NULL);
    assert(powerof2(pagesize_m1 + 1));
    assert(TCB_ALIGNMENT >= STACK_ALIGN);

    // 获取栈的大小,用户没设置就默认
    size = attr->stacksize ?: __default_stacksize;

    // 如果用户在线程属性设置了空间,就直接使用
    if (__builtin_expect(attr->flags & ATTR_FLAG_STACKADDR, 0))
    {
        uintptr_t adj;
        if (attr->stacksize != 0 && attr->stacksize < (__static_tls_size + MINIMAL_REST_STACK))
            return EINVAL;
#if TLS_TCB_AT_TP
        adj = ((uintptr_t)attr->stackaddr - TLS_TCB_SIZE) & __static_tls_align_m1;
        assert(size > adj + TLS_TCB_SIZE);
#elif TLS_DTV_AT_TP
        adj = ((uintptr_t)attr->stackaddr - __static_tls_size) &
              __static_tls_align_m1;
        assert(size > adj);
#endif
#if TLS_TCB_AT_TP
        pd = (struct pthread *)((uintptr_t)attr->stackaddr - TLS_TCB_SIZE - adj);
#elif TLS_DTV_AT_TP
        pd = (struct pthread *)(((uintptr_t)attr->stackaddr - __static_tls_size -
                                 adj) -
                                TLS_PRE_TCB_SIZE);
#endif
        memset(pd, '\0', sizeof(struct pthread));
        pd->specific[0] = pd->specific_1stblock;
        pd->stackblock = (char *)attr->stackaddr - size;
        pd->stackblock_size = size;
        pd->user_stack = true;
        pd->header.multiple_threads = 1;
#ifndef TLS_MULTIPLE_THREADS_IN_TCB
        __pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
#endif
#ifdef NEED_DL_SYSINFO
        THREAD_SYSINFO(pd) = THREAD_SELF_SYSINFO;
#endif
        pd->pid = THREAD_GETMEM(THREAD_SELF, pid);
#ifdef __PTHREAD_MUTEX_HAVE_PREV
        pd->robust_list.__prev = &pd->robust_list;
#endif
        pd->robust_list.__next = &pd->robust_list;
        if (_dl_allocate_tls(TLS_TPADJ(pd)) == NULL)
        {
            assert(errno == ENOMEM);
            return EAGAIN;
        }
        lll_lock(stack_cache_lock);
        list_add(&pd->list, &__stack_user);
        lll_unlock(stack_cache_lock);
    }
    else
    {
        // 下面都是库内部自己做的
        size_t guardsize;
        size_t reqsize;
        void *mem;
        const int prot = (PROT_READ | PROT_WRITE | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));
#if COLORING_INCREMENT != 0
        if (size <= 16 * pagesize_m1)
            size += pagesize_m1 + 1;
#endif
        // 设置空间对齐
        size &= ~__static_tls_align_m1;

        assert(size != 0);
        guardsize = (attr->guardsize + pagesize_m1) & ~pagesize_m1;
        if (__builtin_expect(size < ((guardsize + __static_tls_size +
                                      MINIMAL_REST_STACK + pagesize_m1) &
                                     ~pagesize_m1),
                             0))
            return EINVAL;

        // 先尝试从pthread缓存中申请空间
        reqsize = size;
        pd = get_cached_stack(&size, &mem);
        if (pd == NULL)
        {
#if MULTI_PAGE_ALIASING != 0
            if ((size % MULTI_PAGE_ALIASING) == 0)
                size += pagesize_m1 + 1;
#endif
            // 缓存申请失败, 就在堆空间申请私有的匿名内存空间, 这里mmap类似malloc
            // 也可以作为共享内存的实现
            mem = mmap(NULL, size, prot,
                       MAP_PRIVATE | MAP_ANONYMOUS | ARCH_MAP_FLAGS, -1, 0);
            if (__builtin_expect(mem == MAP_FAILED, 0))
            {
#ifdef ARCH_RETRY_MMAP
                mem = ARCH_RETRY_MMAP(size);
                if (__builtin_expect(mem == MAP_FAILED, 0))
#endif
                    return errno;
            }
            assert(mem != NULL);
#if COLORING_INCREMENT != 0
            unsigned int ncreated = atomic_increment_val(&nptl_ncreated);
            size_t coloring = (ncreated * COLORING_INCREMENT) & pagesize_m1;
            if (__builtin_expect((coloring & __static_tls_align_m1) != 0, 0))
                coloring = (((coloring + __static_tls_align_m1) & ~(__static_tls_align_m1)) & ~pagesize_m1);
#else
#define coloring 0
#endif
            // 在申请的空间中确定struct thread(tcb)的地址
#if TLS_TCB_AT_TP
            pd = (struct pthread *)((char *)mem + size - coloring) - 1;
#elif TLS_DTV_AT_TP
            pd = (struct pthread *)((((uintptr_t)mem + size - coloring -
                                      __static_tls_size) &
                                     ~__static_tls_align_m1) -
                                    TLS_PRE_TCB_SIZE);
#endif
            // 记录下来整个空间的地址和大小
            pd->stackblock = mem;
            pd->stackblock_size = size;
            pd->specific[0] = pd->specific_1stblock;
            pd->header.multiple_threads = 1;
#ifndef TLS_MULTIPLE_THREADS_IN_TCB
            __pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
#endif
#ifdef NEED_DL_SYSINFO

            THREAD_SYSINFO(pd) = THREAD_SELF_SYSINFO;
#endif
            // 获取线程对应进程的PID
            pd->pid = THREAD_GETMEM(THREAD_SELF, pid);

#ifdef __PTHREAD_MUTEX_HAVE_PREV
            pd->robust_list.__prev = &pd->robust_list;
#endif
            pd->robust_list.__next = &pd->robust_list;
            if (_dl_allocate_tls(TLS_TPADJ(pd)) == NULL)
            {
                assert(errno == ENOMEM);
                (void)munmap(mem, size);
                return EAGAIN;
            }
            lll_lock(stack_cache_lock);
            list_add(&pd->list, &stack_used);
            lll_unlock(stack_cache_lock);
            if (__builtin_expect((GL(dl_stack_flags) & PF_X) != 0 && (prot & PROT_EXEC) == 0, 0))
            {
                int err = change_stack_perm(pd
#ifdef NEED_SEPARATE_REGISTER_STACK
                                            ,
                                            ~pagesize_m1
#endif
                );
                if (err != 0)
                {
                    (void)munmap(mem, size);
                    return err;
                }
            }
        }
        if (__builtin_expect(guardsize > pd->guardsize, 0))
        {
#ifdef NEED_SEPARATE_REGISTER_STACK
            char *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);
#else
            char *guard = mem;
#endif
            if (mprotect(guard, guardsize, PROT_NONE) != 0)
            {
                int err;
            mprot_error:
                err = errno;
                lll_lock(stack_cache_lock);
                list_del(&pd->list);
                lll_unlock(stack_cache_lock);
                _dl_deallocate_tls(TLS_TPADJ(pd), false);
                (void)munmap(mem, size);
                return err;
            }
            pd->guardsize = guardsize;
        }
        else if (__builtin_expect(pd->guardsize - guardsize > size - reqsize, 0))
        {
#ifdef NEED_SEPARATE_REGISTER_STACK
            char *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);
            char *oldguard = mem + (((size - pd->guardsize) / 2) & ~pagesize_m1);
            if (oldguard < guard && mprotect(oldguard, guard - oldguard, prot) != 0)
                goto mprot_error;
            if (mprotect(guard + guardsize,
                         oldguard + pd->guardsize - guard - guardsize,
                         prot) != 0)
                goto mprot_error;
#else
            if (mprotect((char *)mem + guardsize, pd->guardsize - guardsize, prot) != 0)
                goto mprot_error;
#endif
            pd->guardsize = guardsize;
        }
        pd->reported_guardsize = guardsize;
    }
    pd->lock = LLL_LOCK_INITIALIZER;
    // 二级指针返回struct thread的地址, 其实就是⼀个堆快的地址
    *pdp = pd;
#if TLS_TCB_AT_TP
    stacktop = ((char *)(pd + 1) - __static_tls_size);
#elif TLS_DTV_AT_TP
    stacktop = (char *)(pd - 1);
#endif
#ifdef NEED_SEPARATE_REGISTER_STACK
    *stack = pd->stackblock;
    *stacksize = stacktop - *stack;
#else
    *stack = stacktop;
#endif
    return 0;
}
  • create_thread
cpp 复制代码
static int create_thread(struct pthread *pd, const struct pthread_attr *attr,STACK_VARIABLES_PARMS)
{
#ifdef TLS_TCB_AT_TP
    assert(pd->header.tcb != NULL);
#endif
    int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
                       | CLONE_DETACHED
#endif
                       | 0);
    if (__builtin_expect(THREAD_GETMEM(THREAD_SELF, report_events), 0))
    {
        const int _idx = __td_eventword(TD_CREATE);
        const uint32_t _mask = __td_eventmask(TD_CREATE);
        if ((_mask & (__nptl_threads_events.event_bits[_idx] | pd -> eventbuf.eventmask.event_bits[_idx])) != 0)
        {
            pd->stopped_start = true;
            int res = do_clone(pd, attr, clone_flags, start_thread, STACK_VARIABLES_ARGS, 1);
            if (res == 0)
            {
                pd->eventbuf.eventnum = TD_CREATE;
                pd->eventbuf.eventdata = pd;
                do
                    pd->nextevent = __nptl_last_event;
                while (atomic_compare_and_exchange_bool_acq(&__nptl_last_event, pd, pd->nextevent) != 0);
                __nptl_create_event();
                lll_unlock(pd->lock);
            }
            return res;
        }
    }
#ifdef NEED_DL_SYSINFO
    assert(THREAD_SELF_SYSINFO == THREAD_SYSINFO(pd));
#endif
    bool stopped = false;
    if (attr != NULL && (attr->cpuset != NULL || (attr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0))
        stopped = true;
    pd->stopped_start = stopped;

    // 真正创建线程
    int res = do_clone(pd, attr, clone_flags, start_thread, STACK_VARIABLES_ARGS, stopped);
    if (res == 0 && stopped)
        lll_unlock(pd->lock);
    return res;
}
  • do_clone
cpp 复制代码
static int do_clone(struct pthread *pd, const struct pthread_attr *attr,int clone_flags, int (*fct)(void *), STACK_VARIABLES_PARMS, int stopped)
{
#ifdef PREPARE_CREATE
    PREPARE_CREATE;
#endif
    if (stopped)
    {
        lll_lock(pd->lock);
    }
    atomic_increment(&__nptl_nthreads);

    // 执行特性体系结构下的clone函数
    if (ARCH_CLONE(fct, STACK_VARIABLES_ARGS, clone_flags, pd, &pd->tid, TLS_VALUE, &pd->tid) == -1)
    {
        atomic_decrement(&__nptl_nthreads);
        if (IS_DETACHED(pd))
        {
            __deallocate_stack(pd);
        }
        return errno;
    }
    // 下面是调用相关系统调用,设置轻量级进程的调度参数和⼀些异常处理
    if (__builtin_expect(stopped != 0, 0))
    {
        INTERNAL_SYSCALL_DECL(err);
        int res = 0;
        if (attr->cpuset != NULL)
        {
            res = INTERNAL_SYSCALL(sched_setaffinity, err, 3, pd->tid, sizeof(cpu_set_t), attr->cpuset);
            if (__builtin_expect(INTERNAL_SYSCALL_ERROR_P(res, err), 0))
            {
                INTERNAL_SYSCALL_DECL(err2);
            err_out:
#if __ASSUME_TGKILL
                (void)INTERNAL_SYSCALL(tgkill, err2, 3, THREAD_GETMEM(THREAD_SELF, pid), pd->tid, SIGCANCEL);
#else
                (void)INTERNAL_SYSCALL(tkill, err2, 2, pd->tid, SIGCANCEL);
#endif
                return (INTERNAL_SYSCALL_ERROR_P(res, err) ? INTERNAL_SYSCALL_ERRNO(res, err) : 0);
            }
        }
        if ((attr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0)
        {
            res = INTERNAL_SYSCALL(sched_setscheduler, err, 3, pd->tid, pd->schedpolicy, &pd->schedparam);
            if (__builtin_expect(INTERNAL_SYSCALL_ERROR_P(res, err), 0))
            {
                goto err_out;
            }
        }
    }
    THREAD_SETMEM(THREAD_SELF, header.multiple_threads, 1);
    return 0;
}

#define ARCH_CLONE __clone
// __clone是glibc用汇编封装的⼀个调⽤clone系统调用的函数
// __clone的实现就是汇编
ENTRY(BP_SYM(__clone))
        movq $- EINVAL,% rax 
        testq % rdi, % rdi 
        jz SYSCALL_ERROR_LABEL 、
        testq % rsi,% rsi 
        jz SYSCALL_ERROR_LABEL
        subq $16,% rsi 
        movq % rcx, 8(% rsi)
        movq % rdi,0(% rsi)
        movq % rdx,% rdi 
        movq % r8, % rdx movq % r9, % r8
        movq 8(% rsp),% r10

        movl $SYS_ify(clone),% eax // 获取系统调用号

        cfi_endproc;
        syscall // 陷⼊内核, 要求内核创建轻量级进程

        testq %rax,% rax
        jl SYSCALL_ERROR_LABEL
        jz L(thread_start)

七、线程封装

使用clone系统调用生成子进程

cpp 复制代码
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024) // 1MB的栈空间

// 子进程执行的函数
static int child_func(void *arg)
{
    while(true)
    {
        printf("子进程PID: %d\n", getpid());
        sleep(1);
    }
    return 0;
}

int main()
{
    char *stack = (char *)malloc(STACK_SIZE); // 为子进程分配栈空间
    if (stack == NULL)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    // 使用clone创建子进程
    pid_t pid = clone(child_func, stack + STACK_SIZE, CLONE_VM | SIGCHLD, NULL);
    if (pid == -1)
    {
        perror("clone");
        free(stack);
        exit(EXIT_FAILURE);
    }

    printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);

    // 等待子进程结束
    if (waitpid(pid, NULL, 0) == -1)
    {
        perror("waitpid");
        free(stack);
        exit(EXIT_FAILURE);
    }
    free(stack);
    return 0;
}

7.1.线程栈

主线程的栈

main函数的栈空间,在fork的时候,复制了父进程的stack空间

然后写时拷贝以及动态增长,扩充超出上限发生栈溢出报段错误

子线程的栈

通过mmap系统调用形成的文件映射区,并不会增长,是固定的

cpp 复制代码
mem = mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

7.2.线程封装的设计

非模板

  • Thread.hpp
cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
#include <unistd.h>

namespace ThreadModlue
{
    // 计数器
    static uint32_t number = 1;
    class Thread
    {
        using func_t = std::function<void()>;

    private:
        void EnableDetach()
        {
            _isdetach = true;
        }

        void EnableRunning()
        {
            _isrunning = true;
        }

        // 在类内实现函数回调
        // static修饰无this指针
        // 通过传this指针获取成员方法
        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);

            self->EnableRunning();

            if (self->_isdetach)
            {
                std::cout << "线程被分离" << std::endl;
                self->Detach();
            }

            self->_func();

            return nullptr;
        }

    public:
        Thread(func_t func)
            : _tid(0), _isdetach(false), _isrunning(false), _res(nullptr), _func(func)
        {
            _name = "thread-" + std::to_string(number++);
        }

        void Detach()
        {
            if (_isdetach)
            {
                return;
            }
            if (_isrunning)
            {
                pthread_detach(_tid);
            }
            EnableDetach();
        }

        bool Start()
        {
            if (_isrunning)
            {
                return false;
            }

            // 创建新线程
            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
                return false;
            }
            else
            {
                std::cout << _name << " create success" << std::endl;
                return true;
            }
        }

        bool Stop()
        {
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "create thread error: " << strerror(n) << std::endl;
                    return false;
                }
                else
                {
                    _isrunning = false;
                    std::cout << _name << " stop" << std::endl;
                    return true;
                }
            }
            return false;
        }

        void Join()
        {
            if (_isdetach)
            {
                std::cout << "你的线程已经分离, 无法进行等待" << std::endl;
                return;
            }

            int n = pthread_join(_tid, &_res);
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
            }
            else
            {
                std::cout << "join success" << std::endl;
            }
        }

        ~Thread()
        {
        }

    private:
        pthread_t _tid;    // 线程ID
        std::string _name; // 线程名称
        bool _isdetach;    // 线程是否分离
        bool _isrunning;   // 线程是否运行
        void *_res;        // 新线程返回值
        func_t _func;      // 新线程的方法
    };
}

#endif
cpp 复制代码
#include "Thread.hpp"
#include <unistd.h>

using namespace ThreadModlue;

int main()
{
    Thread t([](){
        while(true)
        {
            std::cout << "我是一个新线程" << std::endl;
            sleep(1);
        }
    });

    t.Start();
    t.Detach();
    sleep(5);

    t.Stop();
    sleep(5);

    t.Join();

    return 0;
}

含模板

  • Thread.hpp
cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
#include <unistd.h>

namespace ThreadModlue
{
    // 计数器
    static uint32_t number = 1;

    template<typename T>
    class Thread
    {
        using func_t = std::function<void(T)>;

    private:
        void EnableDetach()
        {
            _isdetach = true;
        }

        void EnableRunning()
        {
            _isrunning = true;
        }

        // 在类内实现函数回调
        // static修饰无this指针
        // 通过传this指针获取成员方法
        static void *Routine(void *args)
        {
            Thread<T> *self = static_cast<Thread<T> *>(args);

            self->EnableRunning();
            if (self->_isdetach)
            {
                std::cout << "线程被分离" << std::endl;
                self->Detach();
            }

            self->_func(self->_data);
            return nullptr;
        }

    public:
        Thread(func_t func, T data)
            : _tid(0), _isdetach(false), _isrunning(false), _res(nullptr), _func(func), _data(data)
        {
            _name = "thread-" + std::to_string(number++);
        }

        void Detach()
        {
            if (_isdetach)
            {
                return;
            }
            if (_isrunning)
            {
                pthread_detach(_tid);
            }
            EnableDetach();
        }

        bool Start()
        {
            if (_isrunning)
            {
                return false;
            }

            // 创建新线程
            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
                return false;
            }
            else
            {
                std::cout << _name << " create success" << std::endl;
                return true;
            }
        }

        bool Stop()
        {
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "create thread error: " << strerror(n) << std::endl;
                    return false;
                }
                else
                {
                    _isrunning = false;
                    std::cout << _name << " stop" << std::endl;
                    return true;
                }
            }
            return false;
        }

        void Join()
        {
            if (_isdetach)
            {
                std::cout << "你的线程已经分离, 无法进行等待" << std::endl;
                return;
            }

            int n = pthread_join(_tid, &_res);
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
            }
            else
            {
                std::cout << "join success" << std::endl;
            }
        }

        ~Thread()
        {
        }

    private:
        pthread_t _tid;    // 线程ID
        std::string _name; // 线程名称
        bool _isdetach;    // 线程是否分离
        bool _isrunning;   // 线程是否运行
        void *_res;        // 新线程返回值
        func_t _func;      // 新线程的方法
        T _data;           // 线程的回调参数
    };
}

#endif
cpp 复制代码
#include "Thread.hpp"

using namespace ThreadModlue;

class ThreadData
{
public:
    pthread_t tid;
    std::string name;
};

void Count(ThreadData td)
{
    while(true)
    {
        std::cout << "我是一个新线程" << std::endl;
        sleep(1);
    }
}

int main()
{
    ThreadData td;
    Thread<ThreadData> t(Count, td);
    t.Start();
    t.Join();
    return 0;
}
  • Makefile
cpp 复制代码
thread:Main.cc
	g++ -o $@ $^ -std=c++11 
.PHONY:clean
clean:
	rm -f thread

八、线程的局部存储

8.1.__thread修饰

定义一个全局变量,但不想让该全局变量被其他线程看到,只能存储内部类型和部分指针

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

__thread int count = 1;

std::string Addr(int &c)
{
    char addr[64];
    snprintf(addr, sizeof(addr), "%p", &c);
    return addr;
}

void *routine1(void *args)
{
    (void)args;
    while (true)
    {
        std::cout << "thread - 1, count = " << count << " [我来修改count]" 
        << " &count: " << Addr(count) << std::endl;
        count++;
        sleep(1);
    }
}

void *routine2(void *args)
{
    (void)args;
    while (true)
    {
        std::cout << "thread - 2, count = " << count 
        << " &count: " << Addr(count) << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, nullptr, routine1, nullptr);
    pthread_create(&tid2, nullptr, routine2, nullptr);

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);

    return 0;
}

8.2.pthread_get/setname_np函数

作用:设置/获取线程的名字

pthread_setname_np:将线程名写入线程局部存储区

pthread_getname_np:从线程局部存储区获取线程名

相关推荐
三言老师7 小时前
mkdir创建多级目录,目录创建基础实操
linux·运维·服务器
xian_wwq8 小时前
【学习笔记】解剖 Claude Code —— Anthropic 的 Harness 参考实现-09/15
人工智能·笔记·学习
imc.118 小时前
linux EXT文件系统
linux·运维·服务器
晓梦林8 小时前
Tools靶场学习笔记
笔记·学习
xian_wwq8 小时前
【学习笔记】什么是Harness Engineering- 01/15
人工智能·笔记·学习
笨笨饿9 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
三言老师9 小时前
mv移动文件、重命名文件实战案例
linux·服务器·网络·centos
xian_wwq9 小时前
【学习笔记】Agent 安全护栏 —— 当 Agent DROP TABLE 了你的生产数据库-07/15
笔记·学习·安全
向夏威夷 梦断明暄10 小时前
C# 弃元模式:从语法糖到性能利器的深度解析
服务器·数据库·c#
AOwhisky10 小时前
云原生 DevOps 工具链从入门到实战(第二期)——Jenkins安装与基础配置——CICD核心引擎
linux·运维·ci/cd·云原生·jenkins·devops