多线程(multithreading)使同一进程内存在多条并发执行的指令流,便于重叠计算与 I/O、利用多核处理器。在 POSIX 系统上,C 程序常通过 pthread 库创建与管理线程;线程间共享地址空间,须配合同步机制避免数据竞争。网络服务等场景常与套接字编程结合。
线程与进程
进程是资源分配的单位;线程是进程内的执行单元,同一进程的线程共享代码段与全局数据,但各自拥有独立的栈与寄存器上下文。
- 创建与切换线程的开销通常小于进程。
- 共享内存便于通信,也要求对共享变量进行同步。
- 多线程可在多核上并行执行。
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Thread is running\n");
return NULL;
}
int main(void) {
pthread_t thread;
pthread_create(&thread, NULL, foo, NULL);
pthread_join(thread, NULL);
return 0;
}链接时需指定线程库,例如:
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);参数依次为线程标识符、属性(常传 NULL)、入口函数及传给入口函数的参数。入口函数须为 void *(*)(void *) 类型。
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Created a new thread");
return NULL;
}
int main(void) {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
return 0;
}若主线程在子线程完成前退出且未 join,子线程输出可能无法观察到。
2. pthread_join()
阻塞调用线程,直至指定线程结束:
#include <pthread.h>
#include <stdio.h>
void* foo(void* arg) {
printf("Thread is running.\n");
return NULL;
}
int main(void) {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
pthread_join(thread1, NULL);
return 0;
}3. pthread_exit()
void* foo(void* arg) {
printf("Thread is running.\n");
pthread_exit(NULL);
printf("This statement never executes.\n");
}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(void) {
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()
#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(void) {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
pthread_join(thread1, NULL);
return 0;
}并发问题
- 竞态条件:多线程无同步地读写同一变量,结果依赖调度顺序。
- 死锁:线程相互等待对方持有的锁,均无法继续。
- 饥饿:某线程长期无法获得所需资源。
同步机制概览
| 机制 | 作用 |
|---|---|
| 互斥锁(Mutex) | 保证临界区同一时刻仅一线程进入 |
| 信号量(Semaphore) | 控制可同时访问资源的线程数 |
| 条件变量 | 在条件满足前阻塞线程 |
| 屏障 | 等待所有参与线程到达同步点 |
| 读写锁 | 允许多读单写 |
互斥锁保护共享计数器
#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(void) {
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;
}两线程各递增一百万次,有互斥保护时结果通常为 2000000;若去掉锁,counter++ 非原子,结果可能偏小。
利弊
多线程可提高吞吐与响应性,但增加设计与调试复杂度,须明确共享数据边界并选用适当同步。异步 I/O 与信号处理在并发程序中也需一并考虑。
本节介绍了 pthread 的基本用法与同步需求。下一节说明基于套接字的网络编程流程。