一、 什么是线程?
在 Linux 中,线程是"轻量级进程(LWP)",是 CPU 调度的最小单位。一个进程可以包含多个线程。
-
共享的资源:代码段、数据段(全局变量)、堆区、文件描述符表。
-
私有的资源 :线程 ID、寄存器上下文、独立的栈(Stack)、
errno、信号屏蔽字。线程切换极快(不需要切换页表和刷新 TLB),但也极其危险(一个线程崩溃会导致整个进程崩溃)。
二、 底层存储:为什么需要虚拟内存与多级页表?
操作系统通过"页表"将虚拟地址映射到物理地址。
-
单级页表在 32 位系统下需要占用 4MB 且必须连续,极度浪费。
-
因此引入了多级页表(页目录)。利用局部性原理,只加载需要的页表项。
-
缺页中断(Page Fault) :当 CPU 访问的虚拟地址在页表中不存在时触发。OS 会挂起当前进程,去磁盘把数据读入物理内存,建立映射,然后恢复进程运行。这正是
malloc申请大内存时,"延迟分配"的底层原理。
三、 实战:创建第一个线程

cpp
#include <pthread.h>
pthread_create(&tid, NULL, thread_run, NULL);
注意: 编译时必须加上 -lpthread 链接库。
四、 实战验证与 LWP

使用 ps -aL 查看系统进程和线程信息,可以看到:
- 两个执行流有相同的
PID,但LWP不同。Linux 内核并不区分进程和线程,它们底层都是task_struct,只是共享资源的程度不同。
五、 实战:验证全局变量共享

新建子线程修改全局变量 g_val,主线程随后读取。发现主线程读到了修改后的值,完美验证了线程间数据(数据段)是共享的。
自我实战操作
先不用 C++ 的高大上封装,直接用 C 语言 POSIX 线程库。
bash
cd ~/linux_study
mkdir thread_test
cd thread_test
实战 1:创造一个线程(并观察 LWP)
bash
vim thread_create.c
(输入以下代码)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg) {
while(1) {
printf("我是新线程,我的 ID 是: %lu\n", pthread_self());
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
// 创建线程,执行 thread_run 函数
pthread_create(&tid, NULL, thread_run, NULL);
while(1) {
printf("我是主线程,我的 ID 是: %lu\n", pthread_self());
sleep(1);
}
return 0;
}
编译并运行(注意坑点):
bash
# 必须加 -lpthread,否则会报错 undefined reference to `pthread_create'
gcc thread_create.c -o thread_create -lpthread
./thread_create

👉 现象 :屏幕上会交错打印"主线程"和"新线程"的内容。
👉 神级操作 :新开一个终端,输入:
bash
ps -aL | grep thread_create | head -n 1 && ps -aL | grep thread_create
你会看到什么?
-
有两个名为
thread_create的进程。 -
它们的
PID相同(因为是同一个进程)。 -
它们的
LWP(轻量级进程 ID,也就是线程 ID)不同!(这就证明了:Linux 内核并不区分进程和线程,统一用
task_struct表示,只是共享了某些资源的task_struct就叫做线程。)
实战 2:验证"共享与私有"
bash
vim thread_share.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
int g_val = 100; // 全局变量(共享)
void *thread_run(void *arg) {
g_val = 200; // 子线程修改全局变量
printf("子线程:修改全局变量 g_val = %d\n", g_val);
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
sleep(1); // 等子线程先跑完
printf("主线程:看到全局变量 g_val = %d\n", g_val); // 主线程读全局变量
return 0;
}
bash
gcc thread_share.c -o thread_share -lpthread && ./thread_share

👉 现象 :主线程打印出来的是 200。这证明了全局变量是共享的 。如果是 fork 创建的子进程,这里打印的绝对还是 100(写时拷贝)。这就是线程间通信极其方便、但也极其危险的原因。
