多线程(multithreading)是让一个进程里同时跑多段独立代码的技术。它能让程序一边算数据、一边刷界面,把多核 CPU 用起来。C 语言里最常用的是 POSIX 线程库(pthread),下面从它讲起。
线程是什么
进程是资源分配的基本单位,线程是进程里的一条执行流。一个进程可以开很多线程,它们共享进程的代码、数据、文件,但各自有独立的程序计数器、寄存器和栈。
- 线程比进程轻量,创建和切换更快。
- 共享内存让线程间通信方便,但也带来了同步问题。
- 多个线程可以在多核上真正并行执行。
一个最简单的例子,创建线程跑一段代码:
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Thread is running\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, foo, NULL);
pthread_join(thread, NULL);
return 0;
}输出:
Thread is runningpthread_create() 开一条新线程跑 foo(),主线程用 pthread_join() 等它跑完再继续。编译时记得加 -lpthread:
gcc program.c -lpthread -o programpthread 核心 API
1. pthread_create():创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);四个参数依次是:存线程 ID 的变量、线程属性(一般填 NULL 用默认)、线程要执行的函数、传给这个函数的参数。线程函数签名固定是 void* 返回、void* 参数:
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Created a new thread");
return NULL;
}
int main() {
pthread_t thread1;
/* 创建一条线程执行 foo() */
pthread_create(&thread1, NULL, foo, NULL);
return 0;
}注意:这个程序没加 join,主线程可能先结束,子线程还没来得及打印。
2. pthread_join():等待线程结束
pthread_join() 会阻塞调用它的线程,直到指定的线程终止。主线程通常在 main 结束前 join 所有子线程,避免子线程没跑完程序就退了:
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Thread is running.\n");
return NULL;
}
int main() {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
/* 等线程跑完 */
pthread_join(thread1, NULL);
return 0;
}3. pthread_exit():主动结束线程
线程函数里调用 pthread_exit() 立即终止当前线程,后面的代码不再执行:
void* foo(void* arg) {
printf("Thread is running.\n");
pthread_exit(NULL);
printf("This statement never executes.\n");
}输出:
Thread is running.4. pthread_cancel():请求取消线程
从外面请求结束一条线程。能不能立刻生效取决于线程是否处于可取消状态。常用于让某个一直在忙的线程停下来:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void* myThreadFunc(void* arg) {
while (1) {
printf("Thread is running...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, myThreadFunc, NULL);
sleep(5);
/* 请求取消这条线程 */
pthread_cancel(thread);
/* 等它终止 */
pthread_join(thread, NULL);
printf("Main thread finished.\n");
return 0;
}5. pthread_self():获取当前线程 ID
线程内部可以用 pthread_self() 拿到自己的 ID,方便区分是哪个线程在干活:
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
pthread_t thisThread = pthread_self();
printf("Current thread ID: %lu\n",
(unsigned long)thisThread);
return NULL;
}
int main() {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
pthread_join(thread1, NULL);
return 0;
}多线程的常见误区
- 竞态条件(Race Condition):多个线程同时读写同一份共享数据,结果不可预测。比如两个线程同时给同一个计数器 +1,可能丢更新。
- 死锁(Deadlock):两个线程互相等待对方手里的资源,谁也动不了。线程 A 拿着锁 1 等锁 2,线程 B 拿着锁 2 等锁 1。
- 饥饿(Starvation):某条线程一直抢不到资源被晾在一边,低优先级线程可能被高优先级线程活活饿死。
线程同步
同步是为了保证共享资源一次只被一个线程安全使用。常用机制:
| 机制 | 作用 |
|---|---|
| 互斥锁(Mutex) | 同一时刻只允许一个线程进入临界区 |
| 信号量(Semaphore) | 用计数器控制同时访问资源的线程数量 |
| 条件变量(Condition Variable) | 让线程在某个条件满足前挂起等待 |
| 屏障(Barrier) | 所有线程到齐才一起放行 |
| 读写锁(Read-Write Lock) | 允许多个读者、只允许一个写者 |
用互斥锁保护计数器
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int counter = 0; /* 共享变量 */
pthread_mutex_t lock; /* 互斥锁 */
void* increment(void* arg) {
for (int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&lock);
counter++; /* 临界区:一次一个线程进 */
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL);
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("counter = %d\n", counter);
pthread_mutex_destroy(&lock);
return 0;
}两条线程各加 100 万次,有锁保护结果是 2000000。要是把锁去掉,因为 +1 不是原子操作,结果通常会小于 2000000,这就是竞态。
多线程的利与弊
优点:
- 提升性能、更好利用 CPU,多核机器上优势明显。
- 程序响应更及时,界面不卡顿。
- 资源共享高效,扩展性好。
缺点:
- 设计和实现复杂度高。
- 必须小心同步,否则引入竞态、死锁。
- 线程创建、调度、管理有额外开销。
多线程本质是用复杂度换性能。先想清楚哪些资源要共享、共享时怎么锁,再动手开线程。