资讯详情

Linux多线程编程:从基础到线程池实现

📅 2026/10/31 11:49:04 | 华诺云谱 👁 阅读
Linux多线程编程:从基础到线程池实现
1. Linux线程控制基础在Linux系统中线程是程序执行的最小单位而进程则是资源分配的最小单位。理解线程控制是掌握多线程编程的基础我们先来看一个最基本的线程创建示例#include pthread.h #include stdio.h void* thread_func(void* arg) { printf(New thread created\n); return NULL; } int main() { pthread_t tid; int ret pthread_create(tid, NULL, thread_func, NULL); if(ret ! 0) { perror(pthread_create failed); return 1; } pthread_join(tid, NULL); return 0; }这个简单例子展示了线程创建的基本流程但实际开发中我们需要考虑更多细节。pthread_create函数的四个参数分别是线程ID指针线程属性通常设为NULL使用默认值线程函数传递给线程函数的参数注意创建线程后必须调用pthread_join或pthread_detach否则会导致资源泄漏。join会阻塞当前线程直到目标线程结束而detach则让线程在结束后自动释放资源。1.1 线程属性控制线程属性可以通过pthread_attr_t结构体进行精细控制以下是常见属性的设置示例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);实际项目中合理设置线程属性可以避免许多潜在问题。比如在嵌入式系统中栈大小需要特别关注过小会导致栈溢出过大则浪费内存。2. 线程终止的三种方式线程终止不是简单地让它停止运行而是需要确保资源被正确释放。Linux线程有三种终止方式2.1 从线程函数返回这是最推荐的终止方式线程函数执行完毕自然退出void* thread_func(void* arg) { // 线程工作代码 return (void*)42; // 返回退出码 }返回的值可以通过pthread_join获取void* retval; pthread_join(tid, retval); printf(Thread exited with code %d\n, (int)retval);2.2 调用pthread_exit线程可以在任何地方调用pthread_exit主动退出void* thread_func(void* arg) { if(error_condition) { pthread_exit((void*)1); // 提前退出 } // 正常执行 pthread_exit((void*)0); }警告在主线程中调用pthread_exit会导致程序继续运行直到所有线程结束这可能不是你想要的行为。2.3 被其他线程取消线程可以被同一进程中的其他线程取消pthread_cancel(tid); // 请求取消线程被取消的线程需要在适当的位置设置取消点void* thread_func(void* arg) { // 设置取消类型 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); while(1) { // 这是一个取消点 pthread_testcancel(); // 工作代码 } return NULL; }取消线程是危险操作可能导致资源泄漏应该尽量避免使用。3. 线程异常问题处理多线程环境下的异常处理比单线程复杂得多下面介绍几种常见问题及解决方案。3.1 竞态条件当多个线程同时访问共享资源时可能产生竞态条件。看这个典型例子int counter 0; void* increment(void* arg) { for(int i 0; i 100000; i) { counter; // 这不是原子操作 } return NULL; }运行两个这样的线程后counter的值通常不会如预期的200000。解决方法是用互斥锁pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; int counter 0; void* increment(void* arg) { for(int i 0; i 100000; i) { pthread_mutex_lock(mutex); counter; pthread_mutex_unlock(mutex); } return NULL; }3.2 死锁问题死锁通常发生在多个锁的使用场景中。例如// 线程1 pthread_mutex_lock(mutexA); pthread_mutex_lock(mutexB); // 操作共享资源 pthread_mutex_unlock(mutexB); pthread_mutex_unlock(mutexA); // 线程2 pthread_mutex_lock(mutexB); pthread_mutex_lock(mutexA); // 操作共享资源 pthread_mutex_unlock(mutexA); pthread_mutex_unlock(mutexB);避免死锁的几个原则总是以相同的顺序获取锁使用pthread_mutex_trylock而非阻塞锁设置锁超时时间3.3 条件变量的正确使用条件变量常用于线程间同步但使用不当会导致问题pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond PTHREAD_COND_INITIALIZER; bool ready false; // 等待线程 void* waiter(void* arg) { pthread_mutex_lock(mutex); while(!ready) { pthread_cond_wait(cond, mutex); } // 处理条件满足的情况 pthread_mutex_unlock(mutex); return NULL; } // 通知线程 void* notifier(void* arg) { pthread_mutex_lock(mutex); ready true; pthread_cond_signal(cond); pthread_mutex_unlock(mutex); return NULL; }常见错误包括在调用pthread_cond_wait前未加锁使用if而非while检查条件修改条件后忘记通知4. 线程局部存储线程局部存储(TLS)允许每个线程拥有变量的独立副本非常适合存储线程特定数据__thread int tls_var 0; // GCC扩展语法 void* thread_func(void* arg) { tls_var (int)(long)arg; printf(Thread %ld has tls_var %d\n, (long)pthread_self(), tls_var); return NULL; }POSIX标准也提供了pthread_key_create等函数实现TLSpthread_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)(long)arg; pthread_setspecific(key, data); // 获取线程特定数据 int* value pthread_getspecific(key); printf(Value %d\n, *value); return NULL; }5. 线程安全函数设计编写线程安全函数需要遵循以下原则避免使用全局变量和静态变量如果必须使用共享数据用互斥锁保护使用线程安全版本的库函数如strtok_r替代strtok避免在函数内分配需要调用者释放的内存一个线程安全的计数器实现示例typedef struct { int value; pthread_mutex_t mutex; } ThreadSafeCounter; void counter_init(ThreadSafeCounter* counter) { counter-value 0; pthread_mutex_init(counter-mutex, NULL); } void counter_increment(ThreadSafeCounter* counter) { pthread_mutex_lock(counter-mutex); counter-value; pthread_mutex_unlock(counter-mutex); } int counter_get(ThreadSafeCounter* counter) { pthread_mutex_lock(counter-mutex); int value counter-value; pthread_mutex_unlock(counter-mutex); return value; }6. 线程池实现直接创建销毁线程开销很大线程池是更好的选择。下面是一个简单线程池的实现框架typedef struct { void (*function)(void*); void* argument; } Task; typedef struct { Task* queue; int queue_size; int head; int tail; int count; pthread_mutex_t lock; pthread_cond_t not_empty; pthread_cond_t not_full; } TaskQueue; typedef struct { pthread_t* threads; int thread_count; TaskQueue* queue; int shutdown; } ThreadPool; // 初始化线程池 ThreadPool* thread_pool_init(int thread_count, int queue_size) { ThreadPool* pool malloc(sizeof(ThreadPool)); // 初始化代码... return pool; } // 添加任务 void thread_pool_add_task(ThreadPool* pool, void (*function)(void*), void* arg) { pthread_mutex_lock(pool-queue-lock); // 等待队列有空位 while(pool-queue-count pool-queue-queue_size !pool-shutdown) { pthread_cond_wait(pool-queue-not_full, pool-queue-lock); } if(pool-shutdown) { pthread_mutex_unlock(pool-queue-lock); return; } // 添加任务到队列 pool-queue-queue[pool-queue-tail].function function; pool-queue-queue[pool-queue-tail].argument arg; pool-queue-tail (pool-queue-tail 1) % pool-queue-queue_size; pool-queue-count; pthread_cond_signal(pool-queue-not_empty); pthread_mutex_unlock(pool-queue-lock); } // 工作线程函数 void* worker_thread(void* arg) { ThreadPool* pool (ThreadPool*)arg; 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 pool-queue-count 0) { pthread_mutex_unlock(pool-queue-lock); pthread_exit(NULL); } // 取出任务 Task task pool-queue-queue[pool-queue-head]; pool-queue-head (pool-queue-head 1) % pool-queue-queue_size; pool-queue-count--; pthread_cond_signal(pool-queue-not_full); pthread_mutex_unlock(pool-queue-lock); // 执行任务 (task.function)(task.argument); } return NULL; }这个线程池实现包含了任务队列、工作线程管理和基本的同步机制可以根据实际需求进行扩展。7. 性能优化与调试技巧多线程程序的性能优化和调试有其特殊性下面分享一些实用技巧7.1 锁粒度控制锁的粒度太大锁住太多代码或数据会降低并发性太小则增加锁开销。一个好的经验法则是锁持续时间不超过必要的操作锁保护的数据刚好满足需求读多写少时考虑读写锁// 粗粒度锁 - 不推荐 pthread_mutex_lock(global_lock); // 大量不相关的操作 pthread_mutex_unlock(global_lock); // 细粒度锁 - 更好 pthread_mutex_lock(data1_lock); // 只操作data1 pthread_mutex_unlock(data1_lock); pthread_mutex_lock(data2_lock); // 只操作data2 pthread_mutex_unlock(data2_lock);7.2 避免锁争用锁争用是性能杀手可以通过以下方式减少使用原子操作替代简单锁采用无锁数据结构分区锁将数据分到不同锁保护的区域// 使用原子操作替代锁 #include stdatomic.h atomic_int counter ATOMIC_VAR_INIT(0); void increment() { atomic_fetch_add(counter, 1); }7.3 调试工具Linux下常用的多线程调试工具gdb支持多线程调试常用命令info threads查看所有线程thread n切换到线程nthread apply all bt查看所有线程的调用栈Valgrind检测内存错误和竞态条件valgrind --toolhelgrind ./your_programstrace/ltrace跟踪系统调用和库函数调用perf性能分析工具perf stat ./your_program perf record ./your_program perf report7.4 常见陷阱信号处理信号是发送给整个进程的但哪个线程执行信号处理函数是不确定的。应该尽量避免在多线程程序中使用信号使用专门的信号处理线程通过pthread_sigmask屏蔽其他线程的信号考虑使用signalfd将信号转换为文件描述符事件errno问题errno是线程安全的但下面的代码有问题if(some_call() -1) { // 这里可能被其他线程修改errno printf(Error: %s\n, strerror(errno)); }应该立即保存errnoif(some_call() -1) { int saved_errno errno; printf(Error: %s\n, strerror(saved_errno)); }fork与线程在多线程程序中调用fork只有调用fork的线程被复制到子进程这可能导致死锁其他线程持有的锁在子进程中永远不会被释放。解决方案避免在多线程程序中使用fork如果必须使用fork后立即调用exec使用pthread_atfork注册处理函数8. 现代C中的线程管理虽然本文主要讨论POSIX线程但现代CC11及以上提供了更高级的线程支持#include iostream #include thread #include mutex #include condition_variable #include atomic #include vector std::mutex mtx; std::condition_variable cv; bool ready false; std::atomicint counter(0); void worker(int id) { std::unique_lockstd::mutex lock(mtx); cv.wait(lock, []{ return ready; }); std::cout Worker id started\n; counter; } int main() { std::vectorstd::thread threads; for(int i 0; i 5; i) { threads.emplace_back(worker, i); } { std::lock_guardstd::mutex lock(mtx); ready true; } cv.notify_all(); for(auto t : threads) { t.join(); } std::cout Counter: counter \n; return 0; }C线程库的优点类型安全RAII风格管理锁、线程等更高级的抽象future/promise等可移植性更好但在性能关键或需要精细控制的场景下POSIX线程仍然是必要的。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑