在操作系统层面,线程和进程是两种完全不同的执行实体。理解它们的区别是多线程编程的基础。
进程是操作系统资源分配的基本单位,每个进程都有自己独立的内存空间(包括代码段、数据段、堆栈段等)。当操作系统创建一个新进程时,需要为其分配内存、文件描述符等资源,这个过程开销较大。进程间的通信(IPC)需要通过特定的机制实现,如管道、消息队列、共享内存等。
线程则是进程内的执行单元,一个进程可以包含多个线程。所有线程共享进程的内存空间和资源,包括全局变量、堆内存、文件描述符等。线程的创建和切换开销远小于进程,因为不需要进行内存映射等复杂操作。
从实现角度看,Linux系统中的线程实际上是通过轻量级进程(LWP)实现的,使用相同的系统调用clone()创建,只是参数不同。Windows系统则有专门的线程实现机制。
POSIX线程(pthread)是IEEE制定的线程标准接口,被类Unix系统广泛支持。它定义了一组操作线程的函数和数据类型,主要包括:
pthread不是C标准库的一部分,使用时需要链接pthread库。在Linux系统中,pthread的实现位于glibc中,通过系统调用最终由内核调度器管理线程执行。
下面我们通过一个简单的例子展示如何创建线程:
c复制#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
printf("子线程开始执行\n");
sleep(2); // 模拟耗时操作
printf("子线程执行结束\n");
return NULL;
}
int main() {
pthread_t tid;
int ret;
printf("主线程开始\n");
// 创建线程
ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
// 等待线程结束
pthread_join(tid, NULL);
printf("主线程结束\n");
return 0;
}
编译时需要加上-pthread选项:
bash复制gcc thread_demo.c -o thread_demo -pthread
这个程序展示了多线程编程的基本流程:
pthread_create函数的完整原型如下:
c复制int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数传递是多线程编程中常见的需求。由于线程函数只能接受一个void*参数,当需要传递多个参数时,通常需要封装成结构体:
c复制struct thread_args {
int id;
const char* name;
};
void* thread_func(void* arg) {
struct thread_args* args = (struct thread_args*)arg;
printf("线程%d: %s\n", args->id, args->name);
free(args); // 释放内存
return NULL;
}
int main() {
pthread_t tid;
struct thread_args* args = malloc(sizeof(struct thread_args));
args->id = 1;
args->name = "测试线程";
pthread_create(&tid, NULL, thread_func, args);
pthread_join(tid, NULL);
return 0;
}
线程可以通过以下方式终止:
pthread_exit和return的区别在于:
c复制void* thread_func(void* arg) {
if (some_condition) {
pthread_exit(NULL); // 立即退出
}
// 其他代码
return NULL; // 正常退出
}
线程属性允许我们定制线程的行为,常用的属性包括:
设置线程属性的基本流程:
c复制pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化属性
// 设置分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置栈大小
size_t stack_size = 1024 * 1024; // 1MB
pthread_attr_setstacksize(&attr, stack_size);
// 创建线程
pthread_t tid;
pthread_create(&tid, &attr, thread_func, NULL);
// 销毁属性
pthread_attr_destroy(&attr);
分离线程(detached thread)是指不需要其他线程调用pthread_join来回收资源的线程。分离线程在结束时自动释放资源,适用于不需要获取返回值的场景。
互斥锁是最基本的同步机制,用于保护临界区,防止多个线程同时访问共享资源。
c复制pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_data = 0;
void* thread_func(void* arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mutex);
shared_data++; // 临界区
pthread_mutex_unlock(&mutex);
}
return NULL;
}
互斥锁使用注意事项:
条件变量用于线程间的通知机制,允许线程在某个条件不满足时挂起等待。
典型的生产者-消费者模型实现:
c复制pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0; // 共享缓冲区
void* producer(void* arg) {
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (buffer != 0) { // 缓冲区非空,等待
pthread_cond_wait(&cond, &mutex);
}
buffer = i + 1; // 生产数据
printf("生产: %d\n", buffer);
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 10; i++) {
pthread_mutex_lock(&mutex);
while (buffer == 0) { // 缓冲区为空,等待
pthread_cond_wait(&cond, &mutex);
}
printf("消费: %d\n", buffer);
buffer = 0; // 消费数据
pthread_cond_signal(&cond); // 通知生产者
pthread_mutex_unlock(&mutex);
}
return NULL;
}
读写锁允许多个读操作同时进行,但写操作是独占的。适用于读多写少的场景。
c复制pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int data = 0;
void* reader(void* arg) {
pthread_rwlock_rdlock(&rwlock);
printf("读取数据: %d\n", data);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
void* writer(void* arg) {
int value = *(int*)arg;
pthread_rwlock_wrlock(&rwlock);
data = value;
printf("写入数据: %d\n", data);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
线程安全函数是指可以在多线程环境中安全调用的函数。实现线程安全的常见方法:
可重入函数是线程安全函数的子集,除了满足线程安全外,还要求:
线程局部存储(Thread-Local Storage, TLS)允许每个线程拥有变量的独立副本。
c复制__thread int tls_var = 0; // GCC扩展语法
void* thread_func(void* arg) {
tls_var = *(int*)arg;
printf("线程局部变量: %d\n", tls_var);
return NULL;
}
POSIX标准提供的线程特定数据API:
c复制pthread_key_t key;
void destructor(void* value) {
free(value);
}
void init_key() {
pthread_key_create(&key, destructor);
}
void* thread_func(void* arg) {
int* data = malloc(sizeof(int));
*data = *(int*)arg;
pthread_setspecific(key, data);
int* value = pthread_getspecific(key);
printf("线程特定数据: %d\n", *value);
return NULL;
}
线程池是一种管理多个线程的技术,可以避免频繁创建和销毁线程的开销。
基本线程池实现:
c复制typedef struct {
void (*task)(void*);
void* arg;
} thread_pool_task_t;
typedef struct {
pthread_mutex_t lock;
pthread_cond_t notify;
pthread_t* threads;
thread_pool_task_t* queue;
int thread_count;
int queue_size;
int head;
int tail;
int count;
int shutdown;
} thread_pool_t;
// 初始化线程池
thread_pool_t* thread_pool_create(int thread_count, int queue_size) {
thread_pool_t* pool = malloc(sizeof(thread_pool_t));
// 初始化互斥锁、条件变量等
// 创建worker线程
return pool;
}
// 添加任务
int thread_pool_add_task(thread_pool_t* pool, void (*task)(void*), void* arg) {
pthread_mutex_lock(&pool->lock);
// 检查队列是否已满
// 添加任务到队列
pthread_cond_signal(&pool->notify);
pthread_mutex_unlock(&pool->lock);
return 0;
}
// 销毁线程池
int thread_pool_destroy(thread_pool_t* pool) {
// 设置shutdown标志
// 唤醒所有线程
// 等待所有线程退出
// 释放资源
return 0;
}
bash复制gdb -p <pid>
thread apply all bt # 查看所有线程堆栈
bash复制valgrind --tool=memcheck --leak-check=full ./program
valgrind --tool=helgrind ./program # 检测线程错误
一个简单的多线程Web服务器实现框架:
c复制void handle_client(int client_fd) {
// 处理HTTP请求
char buffer[1024];
read(client_fd, buffer, sizeof(buffer));
// 构造响应
const char* response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Hello, World!";
write(client_fd, response, strlen(response));
close(client_fd);
}
void* worker_thread(void* arg) {
int server_fd = *(int*)arg;
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
handle_client(client_fd);
}
return NULL;
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定和监听
// 创建工作线程
pthread_t threads[4];
for (int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, worker_thread, &server_fd);
}
// 等待线程
for (int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
close(server_fd);
return 0;
}
使用多线程进行矩阵乘法:
c复制#define N 1024
double A[N][N], B[N][N], C[N][N];
struct thread_arg {
int start_row;
int end_row;
};
void* matmul_thread(void* arg) {
struct thread_arg* t_arg = (struct thread_arg*)arg;
for (int i = t_arg->start_row; i < t_arg->end_row; i++) {
for (int j = 0; j < N; j++) {
C[i][j] = 0;
for (int k = 0; k < N; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
return NULL;
}
void parallel_matmul(int thread_count) {
pthread_t threads[thread_count];
struct thread_arg args[thread_count];
int rows_per_thread = N / thread_count;
for (int i = 0; i < thread_count; i++) {
args[i].start_row = i * rows_per_thread;
args[i].end_row = (i == thread_count - 1) ? N : (i + 1) * rows_per_thread;
pthread_create(&threads[i], NULL, matmul_thread, &args[i]);
}
for (int i = 0; i < thread_count; i++) {
pthread_join(threads[i], NULL);
}
}
死锁预防:
竞态条件处理:
性能问题:
C11标准引入了<threads.h>头文件,提供了原生的多线程支持:
c复制#include <threads.h>
int thread_func(void* arg) {
printf("线程运行\n");
return 0;
}
int main() {
thrd_t thread;
thrd_create(&thread, thread_func, NULL);
thrd_join(thread, NULL);
return 0;
}
C++17引入了并行算法,虽然这不是C语言特性,但值得关注:
cpp复制#include <execution>
#include <algorithm>
std::vector<int> v = {...};
std::sort(std::execution::par, v.begin(), v.end());
虽然C语言没有原生协程支持,但可以通过库实现:
c复制#include <coroutine.h>
void producer(coroutine_t* coro) {
while (1) {
int value = produce_value();
coroutine_yield(coro, (void*)(intptr_t)value);
}
}
void consumer() {
coroutine_t* coro = coroutine_create(producer);
while (1) {
int value = (int)(intptr_t)coroutine_resume(coro);
consume_value(value);
}
}
Windows提供了自己的线程API:
c复制#include <windows.h>
DWORD WINAPI thread_func(LPVOID lpParam) {
// 线程代码
return 0;
}
int main() {
HANDLE thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
return 0;
}
为了代码可移植性,可以封装平台相关代码:
c复制#ifdef _WIN32
typedef HANDLE thread_t;
#else
typedef pthread_t thread_t;
#endif
int thread_create(thread_t* thread, void* (*start)(void*), void* arg) {
#ifdef _WIN32
*thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start, arg, 0, NULL);
return (*thread == NULL) ? -1 : 0;
#else
return pthread_create(thread, NULL, start, arg);
#endif
}
bash复制perf stat ./program # 基本统计
perf record ./program && perf report # 采样分析
优化前:
c复制pthread_mutex_t lock;
int counter = 0;
void* thread_func(void* arg) {
for (int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
优化后(使用原子操作):
c复制#include <stdatomic.h>
atomic_int counter = 0;
void* thread_func(void* arg) {
for (int i = 0; i < 1000000; i++) {
atomic_fetch_add(&counter, 1);
}
return NULL;
}
c复制#define CLEANUP(fn) __attribute__((cleanup(fn)))
void mutex_cleanup(pthread_mutex_t** mutex) {
pthread_mutex_unlock(*mutex);
}
void critical_section() {
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
CLEANUP(mutex_cleanup) pthread_mutex_t* lock_ptr = &mutex;
// 临界区代码
// 不需要手动解锁,退出作用域时自动解锁
}
man pthreads在实际项目中应用多线程技术时,我总结了以下几点经验:
一个典型的错误案例是在日志系统中不加控制地多线程写文件,导致日志内容混乱。解决方案是使用单独的日志线程和队列,其他线程通过队列发送日志消息。
多线程编程技术仍在不断发展,值得关注的趋势包括:
虽然这些新技术大多出现在其他语言中,但了解它们有助于我们在C语言项目中做出更好的设计决策。