线程概念与控制
目录
[四、Linux进程 VS 线程](#四、Linux进程 VS 线程)
一、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:从线程局部存储区获取线程名



