thread在C语言语境里就是“线程”的意思,它是操作系统中能够独立调度的最小执行单元。一个进程至少包含一个线程(主线程),也可以创建多个线程让它们并发执行不同的任务。理解thread的含义、掌握线程的创建与使用方式,是C语言进阶到系统编程的重要一步。

一、thread到底指什么:线程与进程的区别
要弄清楚thread的含义,得先从进程说起。进程是操作系统分配资源的基本单位,每个进程拥有独立的地址空间、文件描述符等资源;而线程是进程内部的一条执行流,同一个进程内的多个线程共享进程的堆内存、全局变量和打开的文件,但每个线程拥有自己独立的栈空间和寄存器上下文。
举个例子,一个音乐播放器进程可以分成三个线程:一个负责解码音频,一个负责刷新界面,一个负责响应鼠标键盘事件。它们共享同一份数据,却能各自独立运行,这就是线程的价值——比创建进程更轻量,线程间切换的开销远小于进程切换。
在C语言中并没有一个叫thread的内置关键字(C11之前),通常看到thread这个词,可能出现在三种场景:一是C11标准引入的<threads.h>头文件;二是POSIX系统的pthread_create函数族;三是Windows API的CreateThread。下面分别介绍最常见的两种用法。
二、C11标准线程:threads.h的用法
C11标准首次为C语言定义了跨平台的多线程支持,包含<threads.h>头文件,提供了thrd_create创建线程、thrd_join等待线程等函数。这种写法的好处是与编译器和标准绑定,代码可以在支持C11的平台间移植。
#include <stdio.h>
#include <threads.h>
// 线程执行函数,参数和返回值都是 void* 类型的转型
int my_thread(void *arg) {
int id = *(int *)arg;
printf("线程 %d 正在执行\n", id);
return 0;
}
int main(void) {
thrd_t tid;
int num = 1;
// 创建线程,第三个参数是函数指针
if (thrd_create(&tid, my_thread, &num) != thrd_success) {
printf("创建线程失败\n");
return 1;
}
// 等待线程结束,回收资源
thrd_join(tid, NULL);
printf("主线程继续执行\n");
return 0;
}需要说明的是,C11线程库的实际支持程度因编译器而异。GCC在较新的版本中已经支持threads.h,但如果编译时报错找不到头文件,也可以借助pthread实现,两者思路一致。编译时记得链接线程库,例如使用gcc demo.c -o demo -lpthread(使用pthread时)。
三、pthread线程库:Linux下最常用的方案
在Linux平台,pthread(POSIX线程)是最主流的多线程方案。核心函数是pthread_create,它的原型为int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)。第一个参数用来接收线程ID,第二个参数是线程属性(一般传NULL使用默认值),第三个参数是线程要执行的函数,第四个参数是传给该函数的参数。
#include <stdio.h>
#include <pthread.h>
void *worker(void *arg) {
int n = *(int *)arg;
long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("1 加到 %d 的结果是 %ld\n", n, sum);
return NULL;
}
int main(void) {
pthread_t tid;
int n = 100;
pthread_create(&tid, NULL, worker, &n);
pthread_join(tid, NULL); // 阻塞等待线程结束
printf("主线程退出\n");
return 0;
}这里有一个容易踩坑的地方:传给线程的参数如果指向局部变量,必须保证该变量在线程执行期间一直有效。上面的n是main的局部变量,由于main会等待线程结束才退出,所以没问题;如果主线程提前返回,worker再去访问n就会产生悬空指针,导致未定义行为。
另一个注意点是pthread_join不能省略。主线程退出时整个进程会终止,还没跑完的子线程会被直接杀掉;同时join还负责回收线程资源,不join的话线程资源会一直残留,造成类似内存泄漏的问题。
四、线程安全:多线程编程必须面对的问题
多个线程共享进程的内存空间,这是双刃剑。好处是数据交换方便,坏处是并发访问同一份数据时会出现竞争条件。最经典的例子是两个线程同时对一个全局变量执行count++,由于自增操作实际包含读取、加一、写回三步,线程可能在中途被打断,最终结果比预期小。
解决这类问题的常用手段是互斥锁。pthread提供了pthread_mutex_t类型以及加锁、解锁函数,保证同一时刻只有一个线程进入临界区:
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int count = 0;
void *add(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&lock); // 加锁
count++;
pthread_mutex_unlock(&lock); // 解锁
}
return NULL;
}
int main(void) {
pthread_t t1, t2;
pthread_create(&t1, NULL, add, NULL);
pthread_create(&t2, NULL, add, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("count = %d\n", count); // 稳定输出 200000
return 0;
}除了互斥锁,实际开发中还会用到条件变量、读写锁、信号量等同步工具。初学者建议先把互斥锁理解透,再逐步扩展。另外要养成一个习惯:临界区内的代码尽量短小,只把真正需要保护的操作放进锁内,长时间持锁会严重削弱多线程的并发优势。
总结一下,thread在C语言中指线程,是进程内的并发执行单元。掌握它需要理解进程与线程的关系,熟悉pthread_create、pthread_join这对基础函数,并时刻警惕线程安全问题。把这三块内容吃透,再看生产者消费者模型、线程池等进阶主题就会顺畅很多。