Linux多线程编程:pthread线程创建、传参、同步与资源管理实战
1. 初识Linux多线程编程第一次接触Linux多线程编程时我完全被它的高效性震撼到了。想象一下你的程序可以像交响乐团一样多个乐器线程同时演奏不同的声部最终合奏出美妙的乐章。这就是多线程的魅力所在——它能让程序同时处理多个任务显著提升执行效率。在Linux系统中线程被称为轻量级进程LWP因为它们共享相同的地址空间创建和切换的开销远小于传统进程。一个典型的例子是网络服务器主线程负责监听连接每当有新客户端连接时就创建一个新线程来处理请求。这样服务器就能同时服务多个客户端而不是排队等待。不过多线程编程并非没有挑战。我曾在项目中遇到过这样的问题两个线程同时修改同一个全局变量结果导致数据不一致。这就是典型的竞态条件问题。后来通过使用互斥锁mutex解决了这个问题但这也让我意识到多线程编程需要格外注意线程安全和同步机制。2. 线程创建与参数传递2.1 pthread_create基础用法创建线程是使用pthread库的第一步。pthread_create函数的原型如下#include pthread.h int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);这个函数有四个参数thread用于存储新线程ID的指针attr线程属性通常设为NULL使用默认值start_routine线程要执行的函数arg传递给线程函数的参数下面是一个简单的例子创建线程打印Hello World#include pthread.h #include stdio.h void *print_hello(void *arg) { printf(Hello from thread!\n); return NULL; } int main() { pthread_t thread_id; printf(Before Thread\n); pthread_create(thread_id, NULL, print_hello, NULL); pthread_join(thread_id, NULL); printf(After Thread\n); return 0; }2.2 线程参数传递的陷阱与解决方案给线程传递参数看似简单实则暗藏玄机。最常见的错误是传递局部变量的地址。考虑以下代码void *print_num(void *arg) { int num *(int *)arg; printf(Number: %d\n, num); return NULL; } int main() { pthread_t threads[5]; for (int i 0; i 5; i) { pthread_create(threads[i], NULL, print_num, i); // 危险 } // ... 等待线程结束 return 0; }这段代码的问题在于所有线程都接收同一个变量i的地址而i的值在主线程中不断变化。结果可能是五个线程都打印相同的数字或者出现不可预测的输出。解决方案1直接传递值对于小整数可以将其强制转换为指针传递pthread_create(threads[i], NULL, print_num, (void *)(intptr_t)i);在线程函数中int num (int)(intptr_t)arg;解决方案2动态分配内存对于复杂数据结构应该在堆上分配内存int *num malloc(sizeof(int)); *num i; pthread_create(threads[i], NULL, print_num, num);记得在线程函数结束时释放内存void *print_num(void *arg) { int num *(int *)arg; free(arg); // 释放内存 printf(Number: %d\n, num); return NULL; }解决方案3使用线程局部存储对于需要每个线程有独立副本的数据可以使用thread_local关键字C11及以上或pthread的线程特定数据API。3. 线程同步机制3.1 互斥锁Mutex实战互斥锁是解决竞态条件的基本工具。它就像洗手间的门锁——一次只允许一个人进入。在Linux中使用pthread_mutex_t类型表示互斥锁。基本用法pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; void *thread_func(void *arg) { pthread_mutex_lock(mutex); // 临界区代码 pthread_mutex_unlock(mutex); return NULL; }我曾在日志系统中使用互斥锁。多个线程需要写入同一个日志文件如果不加锁日志内容会混杂在一起。加锁后每个线程的日志消息都能完整写入。常见错误忘记解锁会导致死锁双重锁定同一线程重复加锁除非使用递归锁锁的粒度不当锁住太多代码影响性能锁住太少无法保证安全3.2 条件变量Condition Variable详解条件变量用于线程间的通知机制。它允许线程在某个条件不满足时休眠直到其他线程改变条件并发出通知。典型的生产者-消费者模式pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond PTHREAD_COND_INITIALIZER; int queue_has_data 0; void *consumer(void *arg) { pthread_mutex_lock(mutex); while (!queue_has_data) { pthread_cond_wait(cond, mutex); } // 消费数据 pthread_mutex_unlock(mutex); return NULL; } void *producer(void *arg) { pthread_mutex_lock(mutex); // 生产数据 queue_has_data 1; pthread_cond_signal(cond); pthread_mutex_unlock(mutex); return NULL; }关键点总是使用while循环检查条件而不是if语句条件变量总是与互斥锁配合使用pthread_cond_wait会原子性地释放锁并进入等待4. 线程资源管理4.1 线程回收与分离线程结束后系统不会自动释放其资源必须由其他线程调用pthread_join来回收。这类似于进程中的wait系统调用。pthread_t thread; pthread_create(thread, NULL, thread_func, NULL); // ... 其他工作 void *retval; pthread_join(thread, retval); // 等待线程结束并获取返回值如果不需要关心线程的返回值可以将线程设置为分离状态pthread_attr_t attr; pthread_attr_init(attr); pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); pthread_create(thread, attr, thread_func, NULL); pthread_attr_destroy(attr);或者在创建后分离pthread_detach(thread);分离后的线程结束时系统会自动回收其资源。4.2 线程取消与清理有时需要提前终止线程。pthread_cancel可以发送取消请求但线程需要在取消点才能响应。常见的取消点包括调用pthread_testcancel()执行某些阻塞系统调用如read、write调用sleep为了安全地清理资源可以设置清理处理程序void cleanup_handler(void *arg) { printf(Cleaning up: %s\n, (char *)arg); free(arg); } void *thread_func(void *arg) { char *buffer malloc(1024); pthread_cleanup_push(cleanup_handler, buffer); // 线程工作代码 while (1) { pthread_testcancel(); // 创建取消点 // ... 其他工作 } pthread_cleanup_pop(0); // 非零表示执行清理处理程序 return NULL; }5. 高级线程控制5.1 线程属性定制通过pthread_attr_t可以定制线程的各种属性pthread_attr_t attr; pthread_attr_init(attr); // 设置栈大小通常默认是8MB size_t stack_size 2 * 1024 * 1024; // 2MB pthread_attr_setstacksize(attr, stack_size); // 设置调度策略和优先级 struct sched_param param; param.sched_priority 10; pthread_attr_setschedpolicy(attr, SCHED_RR); pthread_attr_setschedparam(attr, param); pthread_t thread; pthread_create(thread, attr, thread_func, NULL); pthread_attr_destroy(attr);5.2 线程特定数据TSD线程特定数据允许每个线程拥有变量的独立副本。这在非线程安全的库函数改造中特别有用。static pthread_key_t key; void destructor(void *value) { free(value); } void init_key() { pthread_key_create(key, destructor); } void *thread_func(void *arg) { char *data malloc(100); pthread_setspecific(key, data); // 在其他地方获取数据 char *my_data pthread_getspecific(key); // 使用my_data... return NULL; }6. 构建健壮的多线程程序框架结合前面介绍的技术我们可以构建一个健壮的多线程程序框架。以下是一个简单的线程池实现#include pthread.h #include stdlib.h #include stdio.h #define THREAD_COUNT 4 #define QUEUE_SIZE 100 typedef struct { void (*function)(void *); void *argument; } task_t; typedef struct { task_t tasks[QUEUE_SIZE]; int head, tail, count; pthread_mutex_t lock; pthread_cond_t not_empty, not_full; } task_queue_t; typedef struct { pthread_t threads[THREAD_COUNT]; task_queue_t queue; int shutdown; } thread_pool_t; void task_queue_init(task_queue_t *queue) { queue-head queue-tail queue-count 0; pthread_mutex_init(queue-lock, NULL); pthread_cond_init(queue-not_empty, NULL); pthread_cond_init(queue-not_full, NULL); } void thread_pool_init(thread_pool_t *pool) { pool-shutdown 0; task_queue_init(pool-queue); for (int i 0; i THREAD_COUNT; i) { pthread_create(pool-threads[i], NULL, worker_thread, pool); } } void *worker_thread(void *arg) { thread_pool_t *pool (thread_pool_t *)arg; task_t task; while (1) { pthread_mutex_lock(pool-queue.lock); while (pool-queue.count 0 !pool-shutdown) { pthread_cond_wait(pool-queue.not_empty, pool-queue.lock); } if (pool-shutdown) { pthread_mutex_unlock(pool-queue.lock); pthread_exit(NULL); } task pool-queue.tasks[pool-queue.head]; pool-queue.head (pool-queue.head 1) % QUEUE_SIZE; pool-queue.count--; pthread_cond_signal(pool-queue.not_full); pthread_mutex_unlock(pool-queue.lock); // 执行任务 task.function(task.argument); } } int thread_pool_add_task(thread_pool_t *pool, void (*function)(void *), void *arg) { pthread_mutex_lock(pool-queue.lock); while (pool-queue.count QUEUE_SIZE !pool-shutdown) { pthread_cond_wait(pool-queue.not_full, pool-queue.lock); } if (pool-shutdown) { pthread_mutex_unlock(pool-queue.lock); return -1; } pool-queue.tasks[pool-queue.tail].function function; pool-queue.tasks[pool-queue.tail].argument arg; pool-queue.tail (pool-queue.tail 1) % QUEUE_SIZE; pool-queue.count; pthread_cond_signal(pool-queue.not_empty); pthread_mutex_unlock(pool-queue.lock); return 0; } void thread_pool_shutdown(thread_pool_t *pool) { pthread_mutex_lock(pool-queue.lock); pool-shutdown 1; pthread_cond_broadcast(pool-queue.not_empty); pthread_cond_broadcast(pool-queue.not_full); pthread_mutex_unlock(pool-queue.lock); for (int i 0; i THREAD_COUNT; i) { pthread_join(pool-threads[i], NULL); } }这个线程池实现包含了任务队列、工作线程、互斥锁和条件变量等关键组件是一个典型的生产者-消费者模型。在实际项目中可以根据需求扩展更多功能如动态调整线程数量、任务优先级等。