在 Linux 系统中,进程和线程是程序执行的两个核心概念,理解它们对于掌握操作系统的工作原理至关重要。
简单来说,进程是资源分配的基本单位,而线程是CPU调度的基本单位。一个程序在运行时就是一个进程,而一个进程内部可以包含多个可以并发执行的线程。
进程是是程序的一个执行实例,是操作系统进行资源分配和管理的基本实体。你可以把它想象成一个正在运行的、拥有自己独立工作空间的"工厂"。
task_struct 的数据结构(也称为进程控制块 PCB)来描述,它记录了进程的所有信息,如进程ID(PID)、状态、优先级、内存指针等。线程是进程内部的一个执行单元,是 CPU 调度和分派的基本单位。继续上面的比喻,线程就是"工厂"(进程)里的一条条"生产线"。
clone() 系统调用创建,在创建时指定共享部分资源(如内存空间),因此也被称为轻量级进程(LWP)。下表清晰地展示了进程与线程在几个关键维度上的差异:
| 资源分配 | ||
| 调度单位 | ||
| 开销成本 | ||
| 通信方式 | ||
| 崩溃影响 |
POSIX Threads(简称 pthreads)是 IEEE POSIX 1003.1c 标准定义的一套用于多线程编程的 C 语言 API。在 Linux 中,它由 libpthread 库实现(通常通过 -pthread 编译选项链接)。
所有 pthread 函数:
errno**,错误信息直接通过返回值返回;0,失败时返回非零错误码(如 EAGAIN, EINVAL 等);#include <pthread.h>pthread_create — 创建线程功能:创建一个新线程并开始执行指定函数。
#include<pthread.h>intpthread_create(pthread_t *thread,constpthread_attr_t *attr,void *(*start_routine)(void *),void *arg);参数说明:
thread:输出参数,用于保存新线程的 ID(pthread_t 类型)。attr:线程属性(如栈大小、分离状态等),若为 NULL 则使用默认属性。start_routine:线程入口函数,必须是 void* (*)(void*) 类型。arg:传递给入口函数的参数(void* 类型,可传结构体指针)。返回值:
0:成功;0:错误码(如 EAGAIN 资源不足,EINVAL 属性无效)。pthread_join — 等待线程结束(同步回收)功能:阻塞调用线程,直到目标线程终止,并可获取其返回值。
intpthread_join(pthread_t thread, void **retval);参数说明:
thread:要等待的线程 ID。retval:若非 NULL,则用于接收目标线程的返回值(即 pthread_exit() 或 return 的值)。注意:
join;pthread_exit — 主动终止当前线程功能:终止调用线程,并可返回一个值供其他线程通过 pthread_join 获取。
voidpthread_exit(void *retval);参数说明:
retval:线程退出时的返回值(void* 类型)。注意:
return xxx; 等价于 pthread_exit(xxx);pthread_exit 不会终止整个进程,子线程仍可运行。pthread_detach — 设置线程为分离状态功能:将线程标记为"分离(detached)",线程结束后自动释放资源,无需 pthread_join。
intpthread_detach(pthread_t thread);参数说明:
thread:目标线程 ID。返回值:
0:成功;0:错误(如 ESRCH 线程不存在,EINVAL 已是分离状态)。关键点:
“✅ 推荐:创建后立即
pthread_detach(tid),避免资源泄漏。
pthread_cancel — 请求取消线程功能:向目标线程发送"取消请求",但是否响应取决于线程的取消状态和类型。
intpthread_cancel(pthread_t thread);参数说明:
thread:目标线程 ID。返回值:
0:请求成功入队;0:如 ESRCH(线程不存在)。注意:
sleep, read, write, pthread_testcancel)生效;pthread_setcancelstate() 禁用取消。pthread_testcancel — 显式设置取消点功能:如果当前有挂起的取消请求且线程允许取消,则在此处终止线程。
voidpthread_testcancel(void);用途:
pthread_self() — 获取当前线程 IDpthread_tpthread_self(void);pthread_equal() — 比较两个线程 ID 是否相同intpthread_equal(pthread_t t1, pthread_t t2); // 相等返回非0pthread_setcancelstate() / pthread_setcanceltype()PTHREAD_CANCEL_ENABLE/DISABLE)PTHREAD_CANCEL_DEFERRED/ASYNCHRONOUS)#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<string.h>void* worker(void* arg){char* msg = (char*)arg;printf("子线程运行: %s\n", msg);return (void*)"任务完成";}intmain(){pthread_t tid;void* result;int ret = pthread_create(&tid, NULL, worker, "Hello from thread!");if (ret != 0) {fprintf(stderr, "创建线程失败: %s\n", strerror(ret));exit(1); }// 等待线程结束并获取返回值 pthread_join(tid, &result);printf("主线程收到: %s\n", (char*)result);return0;}编译运行:
gcc -o demo demo.c -pthread./demo#include<stdio.h>#include<unistd.h>#include<pthread.h>void* detached_worker(void* arg){printf("分离线程启动,ID: %lu\n", (unsignedlong)pthread_self()); sleep(2);printf("分离线程即将退出(资源自动回收)\n");returnNULL; // 返回值无法被获取}intmain(){pthread_t tid; pthread_create(&tid, NULL, detached_worker, NULL); pthread_detach(tid); // 立即分离printf("主线程继续执行...\n"); sleep(3);printf("主线程结束\n");return0;}“注意:这里没有
pthread_join,程序仍能正常结束且无资源泄漏。
#include<stdio.h>#include<unistd.h>#include<pthread.h>void* cancellable_worker(void* arg){printf("可取消线程启动\n");// 禁用取消(演示用) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); sleep(1);printf("取消被禁用,不会响应取消请求\n");// 启用取消 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);printf("现在启用取消,进入循环...\n");while (1) {// 显式设置取消点 pthread_testcancel();// 模拟工作 usleep(100000); // 0.1秒 }returnNULL;}intmain(){pthread_t tid; pthread_create(&tid, NULL, cancellable_worker, NULL); sleep(2);printf("主线程发送取消请求...\n"); pthread_cancel(tid);void* result; pthread_join(tid, &result);if (result == PTHREAD_CANCELED) {printf("线程已被成功取消!\n"); }return0;}pthread_join | |
pthread_detach | |
pthread_cancel + pthread_testcancel | |
每个线程必须join 或 detach | |
struct,传 struct* |
“⚠️ 重要提醒:
所有 pthread 函数 **不要依赖 errno**,必须检查返回值;编译时务必加 -pthread(不是-lpthread,虽然效果类似,但-pthread更标准);线程安全:共享数据需用互斥锁( pthread_mutex_t)保护。
#include<pthread.h>intpthread_setcanceltype(int type, int *oldtype);功能:
设置当前线程对取消请求的响应方式(即"何时被取消"),前提是该线程的取消状态为 PTHREAD_CANCEL_ENABLE(默认)。
type:新取消类型,取值如下:
sleep, read, write, pthread_testcancel 等)时才会响应取消请求。PTHREAD_CANCEL_DEFERRED(默认)PTHREAD_CANCEL_ASYNCHRONOUSoldtype:用于保存原来的取消类型(可为 NULL)。
0;EINVAL 表示 type 无效)。“📌 异步取消非常危险!因为线程可能在
malloc中间、持有锁时、修改全局数据结构时被杀死,导致:
内存泄漏 死锁(锁未释放) 数据结构损坏 程序崩溃
POSIX 标准只要求以下函数是"异步取消安全"的:
pthread_cancel()pthread_setcancelstate()pthread_setcanceltype()其他库函数(包括 printf, malloc, free)都不是异步取消安全的!
只有以下函数是 POSIX 标准要求的"异步取消安全"函数:
pthread_cancel()pthread_setcancelstate()pthread_setcanceltype()线程处于一个纯计算、无任何系统调用、不访问共享资源、不分配内存的死循环中。
例如:
while (1) { x = x * x + c; // 纯数学计算}此时若用延迟取消,由于没有取消点,线程永远无法响应 pthread_cancel()。这时可临时启用异步取消。
在以下代码中使用异步取消会导致严重问题:
// ❌ 危险!不要在实际代码中使用void* dangerous_thread(void* arg){ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);while (1) {char* p = malloc(1024); // 可能在此处被取消 → 内存泄漏printf("Allocated memory\n"); // 可能在此处被取消 → stdio 缓冲区损坏free(p); usleep(10000); }returnNULL;}⚠️ 后果:程序可能崩溃、内存泄漏、输出乱码,甚至死锁。
// async_cancel.c#include<stdio.h>#include<unistd.h>#include<pthread.h>#include<signal.h>void* compute_thread(void* arg){// 设置为异步取消(⚠️ 仅在此纯计算场景下安全) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);printf("进入纯计算循环(无取消点)...\n");volatiledouble x = 1.0;while (1) { x = x * x + 0.1; // 纯CPU计算,无系统调用// 注意:这里不能有 printf/sleep/malloc 等! }returnNULL;}intmain(){pthread_t tid; pthread_create(&tid, NULL, compute_thread, NULL); sleep(2);printf("主线程发送取消请求...\n"); pthread_cancel(tid); // 异步取消会立即生效void* result; pthread_join(tid, &result);if (result == PTHREAD_CANCELED) {printf("✅ 线程已被异步取消!\n"); }return0;}“✅ 这个例子是安全的,因为线程只做纯计算,不涉及任何资源操作。
// dangerous_async.c (不要运行!仅作演示)#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>void* dangerous_thread(void* arg){ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);while (1) {char* p = malloc(1024); // 可能在此处被取消 → 内存泄漏printf("Allocated memory\n"); // 可能在此处被取消 → stdio 缓冲区损坏free(p); usleep(10000); }returnNULL;}intmain(){pthread_t tid; pthread_create(&tid, NULL, dangerous_thread, NULL); sleep(2); pthread_cancel(tid); pthread_join(tid, NULL);return0;}“⚠️ 后果:程序可能崩溃、内存泄漏、输出乱码,甚至死锁。
pthread_testcancel() 更安全void* safe_compute(void* arg){volatiledouble x = 1.0;while (1) { x = x * x + 0.1; pthread_testcancel(); // 显式取消点,安全! }returnNULL;}这种方式既能让线程响应取消,又保持了高安全性,强烈推荐。
PTHREAD_CANCEL_DEFERRED**(默认);pthread_testcancel()(推荐);malloc/free)read/write)互斥锁(Mutex)是最基本的同步原语,用于保护临界区,确保同一时刻只有一个线程能访问共享资源。
pthread_mutex_init — 初始化互斥锁intpthread_mutex_init(pthread_mutex_t *mutex,constpthread_mutexattr_t *attr);参数说明:
mutex:指向互斥锁变量的指针。attr:互斥锁属性对象,可为 NULL(使用默认属性)。返回值:
0EAGAIN:系统资源不足EINVAL:属性值无效ENOMEM:内存不足注意:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock — 加锁intpthread_mutex_lock(pthread_mutex_t *mutex);参数说明:
mutex:指向互斥锁变量的指针。返回值:
0EDEADLK:检测到死锁(某些实现)EINVAL:mutex 无效行为:
pthread_mutex_unlock — 解锁intpthread_mutex_unlock(pthread_mutex_t *mutex);参数说明:
mutex:指向互斥锁变量的指针。返回值:
0EPERM:当前线程未持有该锁EINVAL:mutex 无效行为:
pthread_mutex_trylock — 尝试加锁(非阻塞)intpthread_mutex_trylock(pthread_mutex_t *mutex);参数说明:
mutex:指向互斥锁变量的指针。返回值:
0EBUSY:锁已被其他线程持有EINVAL:mutex 无效行为:
0EBUSY,不阻塞pthread_mutex_destroy — 销毁互斥锁intpthread_mutex_destroy(pthread_mutex_t *mutex);参数说明:
mutex:指向互斥锁变量的指针。返回值:
0EBUSY:锁当前被某个线程持有EINVAL:mutex 无效注意:
典型使用模式:
#include<pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int shared_data = 0;void* thread_func(void* arg){ pthread_mutex_lock(&mutex);// 临界区开始 shared_data++;// 临界区结束 pthread_mutex_unlock(&mutex);returnNULL;}intmain(){pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); pthread_join(tid, NULL); pthread_mutex_destroy(&mutex); // 清理return0;}关键点:
经典优先级反转问题
最终M先执行,H反而后执行。
“
任务 H(高优先级):需要访问共享资源 R; 任务 L(低优先级):已持有资源 R 的互斥锁; 任务 M(中优先级):不需要资源 R,但会抢占 CPU。
问题:什么是优先级反转?为什么 M(中优先级任务)没有直接访问资源 R,却能阻止高优先级任务 H 的执行?
答案:M 抢占的不是资源 R 本身,而是"持有资源 R 的低优先级任务 L 的 CPU 时间",导致 L 无法释放锁,从而间接阻塞了高优先级任务 H。
为什么会出现这种情况?
资源 R 本身(比如一个全局变量)是被互斥锁保护的,谁持有锁,谁就拥有对 R 的独占访问权。M 根本不需要、也无法直接访问 R,因为它拿不到锁。
但问题出在:L 拿着锁,却因为被 M 抢占而无法运行,导致它迟迟不能释放锁,进而让高优先级的 H 一直等下去。
假设系统采用 抢占式优先级调度(高优先级可打断低优先级):
| L 开始运行 | |||
| H 就绪 | |||
| M 就绪 | |||
| M 持续运行 | H 仍在阻塞等待L 被挂起,无法继续执行,更无法释放锁 | ||
| M 主动放弃 CPU | |||
| L 完成临界区,释放互斥锁 | |||
| H 运行 |
理想情况:在抢占式调度下,最高优先级的就绪任务应该立即运行。
实际情况:
“💡 本质:高优先级任务 H 的执行,被一个优先级比它低的任务 M 所延迟。这颠倒了优先级调度的本意——这就是"反转"的含义。
引入优先级继承互斥锁:
“这样,H 的阻塞时间 = L 的临界区时间(有界),不再受 M 影响。
在多线程程序中,如果对共享资源的访问是 "读多写少" 的模式(例如缓存、配置表、数据库索引),使用普通互斥锁会限制并发读取的能力——即使多个线程只是读数据,也必须串行执行。
✅ 读写锁的核心思想:
“📌 本质:区分读/写操作,精细化控制并发粒度。
所有函数定义在 <pthread.h> 中,需链接 -pthread。
pthread_rwlock_init | |
pthread_rwlock_destroy | |
pthread_rwlock_rdlock | |
pthread_rwlock_tryrdlock | |
pthread_rwlock_wrlock | |
pthread_rwlock_trywrlock | |
pthread_rwlock_unlock |
intpthread_rwlock_init(pthread_rwlock_t *rwlock,constpthread_rwlockattr_t *attr);rwlock:指向读写锁变量的指针。attr:属性对象(可为 NULL 表示默认属性)。intpthread_rwlock_destroy(pthread_rwlock_t *rwlock);“✅ 静态初始化(常用):
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
intpthread_rwlock_rdlock(pthread_rwlock_t *rwlock);intpthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);EBUSY。intpthread_rwlock_wrlock(pthread_rwlock_t *rwlock);intpthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);EBUSY。intpthread_rwlock_unlock(pthread_rwlock_t *rwlock);#include<stdio.h>#include<unistd.h>#include<pthread.h>pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;int shared_data = 0;void* reader(void* id){long tid = (long)id; pthread_rwlock_rdlock(&rwlock);printf("Reader %ld: data = %d\n", tid, shared_data); sleep(1); // 模拟读耗时 pthread_rwlock_unlock(&rwlock);returnNULL;}void* writer(void* id){long tid = (long)id; pthread_rwlock_wrlock(&rwlock); shared_data++;printf("Writer %ld: updated data to %d\n", tid, shared_data); sleep(1); // 模拟写耗时 pthread_rwlock_unlock(&rwlock);returnNULL;}intmain(){pthread_t r1, r2, w1; pthread_create(&r1, NULL, reader, (void*)1); pthread_create(&r2, NULL, reader, (void*)2); pthread_create(&w1, NULL, writer, (void*)1); pthread_join(r1, NULL); pthread_join(r2, NULL); pthread_join(w1, NULL); pthread_rwlock_destroy(&rwlock);return0;}“💡 观察:两个 reader 可能同时打印(读并发),但 writer 会等所有 reader 完成。
默认策略偏向读者(reader-preference):
读者持有锁时间长(如 sleep(1));
高并发读请求源源不断。
“⚠️ 后果:写操作延迟极大,甚至系统"卡死"。
通过设置读写锁属性:
pthread_rwlockattr_t attr;pthread_rwlockattr_init(&attr);pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);pthread_rwlock_init(&rwlock, &attr);pthread_rwlockattr_destroy(&attr);“🔍 说明:
_np表示 non-portable(GNU/Linux 扩展);此策略下:一旦有写者在等待,新读者将被阻塞; 写者按 FIFO 顺序执行,避免饥饿。
rw_semaphore(读写信号量)天然支持写者优先;为什么?
“📌 结论:写饥饿是主要问题,读饥饿几乎无需担心。
| 写饥饿 |
所有函数定义在 <pthread.h> 中,需链接 -pthread。
pthread_spin_init | |
pthread_spin_destroy | |
pthread_spin_lock | |
pthread_spin_trylock | |
pthread_spin_unlock |
“⚠️ 注意:POSIX 自旋锁仅适用于 SMP(多核)系统。在单核系统上,自旋会导致死锁(因为持有锁的线程无法运行)。
intpthread_spin_init(pthread_spinlock_t *lock, int pshared);lock:指向自旋锁变量的指针。pshared:0:仅限同一进程内线程使用(最常见);0:可用于进程间共享(需放在共享内存中,如 mmap 或 shm_open)。ENOMEM)。intpthread_spin_destroy(pthread_spinlock_t *lock);intpthread_spin_lock(pthread_spinlock_t *lock);intpthread_spin_trylock(pthread_spinlock_t *lock);0,失败(已被占用)返回 EBUSY。intpthread_spin_unlock(pthread_spinlock_t *lock);虽然 POSIX 标准不规定具体实现,但典型实现依赖于 CPU 原子指令:
使用 Compare-and-Swap (CAS) 或 Test-and-Set (TAS) 原子操作;
示例伪代码:
while (!atomic_compare_exchange_strong(&lock->val, &expected, 1)) {// 忙等待,可能插入 pause 指令优化__asm__ volatile("pause"); // x86 提示 CPU 这是自旋循环}在 Linux 上,glibc 的 pthread_spin_lock 最终调用内联汇编或原子内置函数(如 __sync_lock_test_and_set)。
“🔍 关键点:整个"检查+设置"操作是原子的,防止多个线程同时认为锁空闲。
int),实现简洁 | |
“📌 经验法则:临界区执行时间 < 1 微秒 时考虑自旋锁;否则用互斥锁。
#include<stdio.h>#include<pthread.h>#include<unistd.h>pthread_spinlock_t spinlock;volatileint counter = 0;void* worker(void* arg){for (int i = 0; i < 100000; i++) { pthread_spin_lock(&spinlock); counter++; // 极短临界区 pthread_spin_unlock(&spinlock); }returnNULL;}intmain(){ pthread_spin_init(&spinlock, 0); // 进程内使用pthread_t t1, t2; pthread_create(&t1, NULL, worker, NULL); pthread_create(&t2, NULL, worker, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL);printf("Final counter: %d\n", counter); // 应为 200000 pthread_spin_destroy(&spinlock);return0;}“💡 编译:
gcc -o spin spin.c -pthread
“**POSIX 自旋锁是一把"双刃剑"**:
✅ 用对了:极致性能,超低延迟; ❌ 用错了:CPU 空转,系统卡顿。 建议:除非你明确知道临界区非常短且运行在多核系统上,否则优先使用
pthread_mutex_t。自旋锁更适合底层库开发者或性能调优专家使用。
互斥锁(Mutex)可以保护共享数据不被并发破坏,但它**无法解决"等待某个条件成立"**的问题。
“"我想在
buffer_not_empty为真时才从缓冲区取数据,但我不知道什么时候它会变真。"
如果用轮询(busy-waiting):
while (buffer_empty) {// 空转,浪费 CPU}效率极低!
“🔑 核心思想:**"等待 + 通知"机制**,必须与互斥锁配合使用。
所有函数定义在 <pthread.h> 中,需链接 -pthread。
pthread_cond_init | |
pthread_cond_destroy | |
pthread_cond_wait | |
pthread_cond_timedwait | |
pthread_cond_signal | |
pthread_cond_broadcast |
“✅ 静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
intpthread_cond_init(pthread_cond_t *cond,constpthread_condattr_t *attr);cond:指向条件变量的指针;attr:属性(通常为 NULL,使用默认)。intpthread_cond_destroy(pthread_cond_t *cond);EBUSY。intpthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);⚠️ 原子性操作(这是精髓!):
mutex**;cond 的等待队列并阻塞;mutex**。“📌 必须在调用前已持有
mutex!
intpthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);abstime 是绝对时间(如 CLOCK_REALTIME),不是相对超时;ETIMEDOUT。intpthread_cond_signal(pthread_cond_t *cond); // 唤醒一个intpthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有pthread_mutex_lock(&mutex);// 必须用 while 循环检查条件!(防虚假唤醒)while (condition_is_false) { pthread_cond_wait(&cond, &mutex);}// 此时条件成立,操作共享数据do_something();pthread_mutex_unlock(&mutex);while 而不是 if?signal,线程也可能被唤醒(POSIX 允许);broadcast 唤醒,但条件可能只满足一个。“✅ 永远用
while检查条件!
#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];int count = 0; // 当前元素个数int in = 0, out = 0;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t not_full = PTHREAD_COND_INITIALIZER; // 缓冲区不满pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER; // 缓冲区不空void* producer(void* arg){for (int i = 0; i < 20; i++) { pthread_mutex_lock(&mutex);// 缓冲区满?等待while (count == BUFFER_SIZE) { pthread_cond_wait(¬_full, &mutex); } buffer[in] = i; in = (in + 1) % BUFFER_SIZE; count++;printf("Produced: %d\n", i); pthread_cond_signal(¬_empty); // 通知消费者 pthread_mutex_unlock(&mutex); usleep(100000); // 模拟生产耗时 }returnNULL;}void* consumer(void* arg){for (int i = 0; i < 20; i++) { pthread_mutex_lock(&mutex);// 缓冲区空?等待while (count == 0) { pthread_cond_wait(¬_empty, &mutex); }int item = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--;printf("Consumed: %d\n", item); pthread_cond_signal(¬_full); // 通知生产者 pthread_mutex_unlock(&mutex); usleep(150000); // 模拟消费耗时 }returnNULL;}intmain(){pthread_t prod, cons; pthread_create(&prod, NULL, producer, NULL); pthread_create(&cons, NULL, consumer, NULL); pthread_join(prod, NULL); pthread_join(cons, NULL);// 清理(静态初始化通常可省略 destroy)return0;}“💡 关键点:
两个条件变量: not_full和not_empty;使用 while防止虚假唤醒;signal唤醒对方角色。
timedwait)#include<stdio.h>#include<time.h>#include<pthread.h>pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int ready = 0;void* waiter(void* arg){structtimespectimeout; clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_sec += 3; // 3秒后超时 pthread_mutex_lock(&mtx);while (!ready) {int ret = pthread_cond_timedwait(&cond, &mtx, &timeout);if (ret == ETIMEDOUT) {printf("Wait timed out!\n");break; } }if (ready) {printf("Condition met!\n"); } pthread_mutex_unlock(&mtx);returnNULL;}void* setter(void* arg){ sleep(2); // 2秒后设置条件 pthread_mutex_lock(&mtx); ready = 1; pthread_cond_signal(&cond); pthread_mutex_unlock(&mtx);returnNULL;}intmain(){pthread_t t1, t2; pthread_create(&t1, NULL, waiter, NULL); pthread_create(&t2, NULL, setter, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL);return0;}“输出:
Condition met!(因为 2s < 3s)
wait 前必须持有锁;while 检查条件;signal/broadcast 可在锁内或锁外调用(通常在锁内,保证状态一致性);| 核心价值 | |
| 关键函数 | waitsignal, broadcast |
| 必须搭配 | |
| 经典模式 | while (!condition) wait(...) |
| 典型应用 |
“💡 记住:条件变量不是"条件",而是"等待条件变化的机制"。真正的条件由你的共享变量定义。
通过合理使用 POSIX 条件变量,你可以构建高效、可扩展的多线程协作程序,避免轮询带来的资源浪费。
信号量(Semaphore)本质上是一个非负整数计数器,配合两个原子操作实现线程/进程间的同步机制:
sem_wait):申请资源,计数器减 1;若值为 0 则阻塞。sem_post):释放资源,计数器加 1;若有等待者则唤醒一个。所有操作都是原子的,由操作系统或硬件保证,不会被中断。
信号量的初始值)> 1(如 3、5、10),表示可用资源的数量。
典型场景:
BUFFER_SIZE |
特点:允许多个任务同时成功获取信号量(只要计数器 > 0)。
信号量的初始值 = 1,此时它退化为一个二值信号量(Binary Semaphore),实现互斥访问。
特点:任意时刻最多只有一个线程能持有该信号量。
| 初始值 | ||
| 用途 | ||
| 并发性 | ||
| 类比 | ||
| 是否等同于 Mutex? |
“⚠️ 重要区别:虽然二值信号量可以实现互斥,但它不是互斥锁(Mutex)!
**Mutex 有"所有权"**:只有加锁的线程能解锁; 信号量无所有权:任何线程都可以 sem_post(即使它没wait过);Mutex 支持优先级继承,信号量不支持。
无名信号量(Unnamed Semaphore),也称内存信号量,具有以下特点:
sem_init() 初始化,sem_destroy() 销毁;“📌 本质:无名信号量是一个
sem_t类型的变量,存在于进程内存中。
sem_init — 初始化无名信号量#include<semaphore.h>intsem_init(sem_t *sem, int pshared, unsignedint value);参数说明:
sem:指向信号量变量的指针。pshared:共享标志0:仅限同一进程内的线程共享(最常见)0:可在不同进程间共享(需配合共享内存,如 mmap 或 shm_open)value:信号量的初始值(非负整数)返回值:
0-1,并设置 errnoEINVAL:value 超过 SEM_VALUE_MAXENOSYS:系统不支持 pshared 非 0⚠️ 注意:
sem_destroy — 销毁无名信号量intsem_destroy(sem_t *sem);参数说明:
sem:指向信号量变量的指针返回值:
0-1,并设置 errnoEINVAL:sem 不是有效的信号量EBUSY:有进程/线程正在等待该信号量⚠️ 注意:
sem_wait / sem_trywait — 等待/获取信号量intsem_wait(sem_t *sem); // 阻塞等待intsem_trywait(sem_t *sem); // 非阻塞尝试sem_wait 行为:
sem_trywait 行为:
0-1,设置 errno = EAGAIN返回值:
0-1,设置 errnoEAGAIN(仅 sem_trywait):信号量值为 0EINTR:调用被信号中断EINVAL:sem 不是有效的信号量sem_post — 释放信号量intsem_post(sem_t *sem);行为:
返回值:
0-1,设置 errnoEINVAL:sem 不是有效的信号量EOVERFLOW:信号量值超过 SEM_VALUE_MAX⚠️ 重要:此函数是异步信号安全的(async-signal-safe),可以在信号处理函数中调用。
场景 1:线程间同步(最常见)
场景 2:进程间同步(高级用法)
pshared=1 + 共享内存// unnamed_sem_thread.c#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<semaphore.h>#include<unistd.h>sem_t sem;void* consumer(void* arg){printf("消费者: 等待信号...\n"); sem_wait(&sem); // 等待生产者发送信号printf("消费者: 收到信号,开始处理\n");returnNULL;}void* producer(void* arg){ sleep(2); // 模拟生产耗时printf("生产者: 发送信号\n"); sem_post(&sem); // 唤醒消费者returnNULL;}intmain(){// 初始化无名信号量,初始值 0(消费者开始时需等待)if (sem_init(&sem, 0, 0) == -1) { perror("sem_init failed");exit(1); }pthread_t cons, prod; pthread_create(&cons, NULL, consumer, NULL); pthread_create(&prod, NULL, producer, NULL); pthread_join(cons, NULL); pthread_join(prod, NULL); sem_destroy(&sem); // 销毁信号量printf("主线程: 完成\n");return0;}编译运行:
gcc -o unnamed_sem_thread unnamed_sem_thread.c -pthread -lrt./unnamed_sem_thread预期输出:
消费者: 等待信号...生产者: 发送信号消费者: 收到信号,开始处理主线程: 完成“💡 说明:生产者线程 sleep 2 秒后调用
sem_post,消费者在sem_wait处阻塞直到收到信号。
// unnamed_sem_process.c#include<stdio.h>#include<stdlib.h>#include<sys/mman.h>#include<semaphore.h>#include<unistd.h>#include<sys/wait.h>#include<fcntl.h>#define SHM_SIZE sizeof(sem_t)intmain(){// 创建共享内存int shm_fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);if (shm_fd == -1) { perror("shm_open failed");exit(1); }// 设置共享内存大小if (ftruncate(shm_fd, SHM_SIZE) == -1) { perror("ftruncate failed");exit(1); }// 映射共享内存sem_t *sem = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (sem == MAP_FAILED) { perror("mmap failed");exit(1); }// 初始化无名信号量,pshared=1(进程间共享)if (sem_init(sem, 1, 0) == -1) { perror("sem_init failed");exit(1); }pid_t pid = fork();if (pid == 0) {// 子进程(消费者)printf("子进程: 等待信号...\n"); sem_wait(sem);printf("子进程: 收到信号,开始处理\n"); munmap(sem, SHM_SIZE); close(shm_fd);exit(0); } elseif (pid > 0) {// 父进程(生产者) sleep(2);printf("父进程: 发送信号\n"); sem_post(sem); wait(NULL); // 等待子进程结束 sem_destroy(sem); munmap(sem, SHM_SIZE); close(shm_fd); shm_unlink("/myshm"); // 删除共享内存printf("父进程: 完成\n"); } else { perror("fork failed");exit(1); }return0;}编译运行时需要链接 librt:
gcc -o unnamed_sem_process unnamed_sem_process.c -lrt -pthread./unnamed_sem_process预期输出:
子进程: 等待信号...父进程: 发送信号子进程: 收到信号,开始处理父进程: 完成“⚠️ 注意:
需要 pshared=1才能在进程间共享必须配合共享内存( shm_open+mmap)父子进程通过映射到同一块共享内存来访问同一个信号量 清理步骤: sem_destroy→munmap→close→shm_unlink
有名信号量(Named Semaphore),也称文件系统信号量,具有以下特点:
/dev/shm 目录)sem_open() 打开/创建,sem_close() 关闭,sem_unlink() 删除sem_unlink 删除)“📌 本质:有名信号量是文件系统中的一个命名对象,类似于文件,但由内核管理。
命名规则:
/ 开头(POSIX 标准)"/mysem", "/app_sem_1", "/database_lock""mysem", "./sem", "sem_name"/ 开头,后续不能再有 /NAME_MAX 限制(通常为 255 字符)文件系统位置:
/dev/shm/(tmpfs 临时文件系统)ls /dev/shm/ 查看所有有名信号量示例:
# 创建名为 "/my_semaphore" 的有名信号量后# 可在 /dev/shm/ 中看到:ls /dev/shm/sem.my_semaphore# 手动清理(如果程序异常退出)rm /dev/shm/sem.my_semaphore# 或在代码中使用 sem_unlink("/my_semaphore")“⚠️ 重要:有名信号量名称在文件系统中的实际格式是
sem.+ 信号量名(Linux 特定实现),但sem_open()和sem_unlink()只需使用原始名称(如"/my_semaphore")。
sem_open — 打开或创建有名信号量#include<semaphore.h>#include<fcntl.h>sem_t *sem_open(constchar *name, int oflag, ...);参数说明:
name:信号量名称,格式必须是 "/semname"(POSIX 要求以 / 开头)oflag:打开标志,可以是以下值的组合:0:仅打开已存在的信号量(不存在则失败)O_CREAT:不存在则创建,存在则打开O_EXCL:需与 O_CREAT 联用,如果信号量已存在则失败(避免重复创建)oflag 包含 O_CREAT 时提供):mode_t mode:权限模式(八进制,如 0666, 0600)unsigned int value:信号量初始值返回值:
sem_t 的指针SEM_FAILED(相当于 (sem_t *)-1),并设置 errnoEEXIST:O_CREAT | O_EXCL 指定,但信号量已存在ENOENT:信号量不存在(且未指定 O_CREAT)EACCES:权限不足ENAMETOOLONG:名称过长EINVAL:名称无效示例:
// 创建名为 "/mysem" 的有名信号量,初始值 1sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1);if (sem == SEM_FAILED) { perror("sem_open failed");exit(1);}“✅ 最佳实践:始终检查返回值是否为
SEM_FAILED,而不是NULL。
sem_close — 关闭有名信号量intsem_close(sem_t *sem);行为:
sem_open 调用对应一个引用)返回值:
0-1,设置 errnoEINVAL:sem 不是有效的信号量⚠️ 重要区别:
sem_close()不会删除信号量,只是关闭当前进程的句柄sem_unlink)sem_open() 调用都应该有对应的 sem_close() 调用sem_unlink — 删除有名信号量intsem_unlink(constchar *name);行为:
返回值:
0-1,设置 errnoENOENT:信号量不存在EACCES:权限不足ENAMETOOLONG:名称过长⚠️ 典型用法:
// 创建进程(守护进程)sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1);// ... 使用信号量 ...sem_close(sem);sem_unlink("/mysem"); // 守护进程退出前删除名称// 客户端进程sem_t *sem = sem_open("/mysem", 0); // 只打开,不创建// ... 使用信号量 ...sem_close(sem);sem_wait / sem_trywait / sem_post这些函数与无名信号量的函数完全相同,但作用于有名信号量的句柄。
intsem_wait(sem(sem_t *sem);int sem_trywait(sem_t *sem);int sem_post(sem_t *sem);功能、返回值、行为与无名信号量版本一致,见 5.2.3.2 节。
场景 1:守护进程与客户端进程同步
场景 2:多个独立服务实例协调
场景 3:单例模式实现
O_CREAT | O_EXCL 标志尝试创建有名信号量errno == EEXIST),说明已有实例运行sem_unlink() 删除信号量// named_sem_daemon.c#include<stdio.h>#include<stdlib.h>#include<semaphore.h>#include<unistd.h>#include<string.h>#define SEM_NAME "/service_sem_1"voiddaemon_process(){printf("守护进程: 启动...\n");// 创建有名信号量,初始值 1(互斥)sem_t *sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0666, 1);if (sem == SEM_FAILED) {if (errno == EEXIST) {printf("守护进程: 信号量已存在,可能有其他实例在运行\n"); sem = sem_open(SEM_NAME, 0); // 打开现有信号量if (sem == SEM_FAILED) { perror("sem_open failed");exit(1); } } else { perror("sem_open failed");exit(1); } } else {printf("守护进程: 创建了新信号量\n"); }// 模拟提供服务for (int i = 0; i < 5; i++) { sleep(1);printf("守护进程: 正在提供服务...\n"); } sem_close(sem);printf("守护进程: 关闭信号量\n");// 删除信号量名称(可选,根据需求) sem_unlink(SEM_NAME);printf("守护进程: 删除信号量,退出\n");}voidclient_process(int id){printf("客户端 %d: 启动...\n", id);// 打开已有的有名信号量(不创建)sem_t *sem = sem_open(SEM_NAME, 0);if (sem == SEM_FAILED) { perror("sem_open failed");exit(1); }printf("客户端 %d: 等待服务...\n", id); sem_wait(sem);printf("客户端 %d: 获得服务,处理中...\n", id); sleep(2); sem_post(sem);printf("客户端 %d: 释放服务\n", id); sem_close(sem);printf("客户端 %d: 退出\n", id);}intmain(int argc, char *argv[]){if (argc != 2) {printf("用法: %s <daemon|client>\n", argv[0]);return1; }if (strcmp(argv[1], "daemon") == 0) { daemon_process(); } elseif (strcmp(argv[1], "client") == 0) { client_process(getpid()); } else {printf("无效参数: %s\n", argv[1]);return1; }return0;}编译运行:
gcc -o named_sem_daemon named_sem_daemon.c -lrt# 终端 1:启动守护进程./named_sem_daemon daemon# 终端 2:启动客户端 1./named_sem_daemon client# 终端 3:启动客户端 2./named_sem_daemon client预期输出(守护进程终端):
守护进程: 启动...守护进程: 创建了新信号量守护进程: 正在提供服务...守护进程: 正在提供服务......守护进程: 关闭信号量守护进程: 删除信号量,退出预期输出(客户端终端):
客户端 1: 启动...客户端 1: 等待服务...客户端 1: 获得服务,处理中...客户端 1: 释放服务客户端 1: 退出“💡 说明:
守护进程使用 O_CREAT | O_EXCL创建信号量客户端使用 0标志打开现有信号量sem_unlink()在守护进程退出时调用(可选)
// named_sem_singleton.c#include<stdio.h>#include<stdlib.h>#include<semaphore.h>#include<unistd.h>#include<signal.h>#define SEM_NAME "/app_singleton_sem"sem_t *singleton_sem = NULL;// 信号处理函数voidcleanup_handler(int signum){if (singleton_sem != NULL) { sem_close(singleton_sem); sem_unlink(SEM_NAME);printf("程序正常退出\n"); }exit(0);}intmain(){// 注册信号处理函数(优雅退出) signal(SIGINT, cleanup_handler); signal(SIGTERM, cleanup_handler);printf("检查是否已有实例运行...\n");// 尝试创建有名信号量(O_EXCL 确保唯一性) singleton_sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0666, 1);if (singleton_sem == SEM_FAILED) {if (errno == EEXIST) {printf("错误:已有程序实例在运行!\n");printf("请先关闭现有实例,或删除信号量:sem_unlink(\"%s\")\n", SEM_NAME);return1; } else { perror("sem_open failed");return1; } }printf("成功:这是唯一的程序实例\n");// 模拟程序主循环printf("程序运行中,按 Ctrl+C 退出...\n");for (int i = 1; ; i++) { sleep(1);printf("运行中... %d 秒\n", i); }// 理论上不会执行到这里(通过信号处理函数退出)return0;}编译运行:
gcc -o named_sem_singleton named_sem_singleton.c -lrt# 终端 1:启动第一个实例./named_sem_singleton# 终端 2:尝试启动第二个实例./named_sem_singleton预期输出(终端 1 - 第一个实例):
检查是否已有实例运行...成功:这是唯一的程序实例程序运行中,按 Ctrl+C 退出...运行中... 1 秒运行中... 2 秒...预期输出(终端 2 - 第二个实例):
检查是否已有实例运行...错误:已有程序实例在运行!请先关闭现有实例,或删除信号量:sem_unlink("/app_singleton_sem")“💡 说明:
使用 O_CREAT | O_EXCL确保信号量唯一性如果创建失败且 errno == EEXIST,说明已有实例信号处理函数确保程序退出时清理信号量 如果程序异常退出,可手动清理: rm /dev/shm/sem.app_singleton_sem
| 存储位置 | /dev/shm) | |
| 生命周期 | sem_unlink 删除) | |
| 可见范围 | ||
| 初始化方式 | sem_init() | sem_open(O_CREAT) |
| 清理方式 | sem_destroy() | sem_unlink() |
| 进程间支持 | pshared=1) | |
| API 复杂度 | ||
| 性能 | ||
| 命名机制 | /semname 格式) | |
| 权限控制 | mode 参数) | |
| 典型应用 | ||
| 并发原语 | sem_wait/post | sem_wait/post/open/close/unlink |
详细说明:
存储位置与生命周期:
sem_unlink() 删除可见范围:
进程间支持:
pshared=1 + 共享内存(mmap 或 shm_open),实现较复杂性能:
初始化与清理:
sem_init() → 使用 → sem_destroy()sem_open() → 使用 → sem_close() → sem_unlink()错误处理:
-1 + errno 表示sem_open() 失败返回 SEM_FAILED(不是 NULL),其他类似问题 1:需要同步的是线程还是进程?
问题 1:需要同步的是线程还是进程?├─ 线程 → 无名信号量(pshared=0)└─ 进程 → 继续问题 2问题 2:进程间是否为父子关系(通过 fork 创建)?
问题 2:进程间是否为父子关系(fork)?├─ 是 → 继续问题 3└─ 否(独立进程) → 有名信号量问题 3:是否可以使用共享内存?
问题 3:是否可以使用共享内存?├─ 有共享内存可用 → 无名信号量(pshared=1,性能更好)└─ 无 → 有名信号量问题 4:需要信号量在所有进程退出后仍然存在?
问题 4:需要信号量在所有进程退出后仍然存在?├─ 是 → 有名信号量└─ 否 → 无名信号量(配合共享内存)完整决策流程图:
开始 │ ├─ 线程间同步? │ ├─ 是 → 无名信号量(pshared=0) │ └─ 否 → 进程间同步? │ ├─ 是 → 父子进程关系? │ │ ├─ 是 → 有共享内存? │ │ │ ├─ 是 → 无名信号量(pshared=1) │ │ │ └─ 否 → 有名信号量 │ │ └─ 否(独立进程)→ 有名信号量 │ └─ 否 → (不应到达) │ └─ 结束1. 线程编程
✅ 推荐:无名信号量
sem_t sem;sem_init(&sem, 0, 1); // pshared=0,线程间// 使用sem_wait(&sem);// 临界区sem_post(&sem);// 清理sem_destroy(&sem);特点:
2. 进程间同步(fork 子进程)
✅ 首选:无名信号量 + 共享内存(性能更好)
// 创建共享内存int shm_fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);ftruncate(shm_fd, sizeof(sem_t));sem_t *sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP)SHARE, shm_fd, 0);// 初始化,pshared=1sem_init(sem, 1, 1);pid_t pid = fork();if (pid == 0) {// 子进程 sem_wait(sem);// 临界区 sem_post(sem); munmap(sem, sizeof(sem_t)); close(shm_fd);exit(0);} else {// 父进程 sem_wait(sem);// 临界区 sem_post(sem); wait(NULL); sem_destroy(sem); munmap(sem, sizeof(sem_t)); close(shm_fd); shm_unlink("/myshm");}⚖️ 备选:有名信号量(简单但稍慢)
// 父进程sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1);// ... 使用 ...sem_close(sem);sem_unlink("/mysem");// 子进程sem_t *sem = sem_open("/mysem", 0);// ... 使用 ...sem_close(sem);3. 独立进程间同步
✅ 必须使用:有名信号量
// 进程 Asem_t *sem = sem_open("/shared_sem", O_CREAT, 0666, 1);// ... 使用 ...sem_close(sem);sem_unlink("/shared_sem");// 进程 B(独立启动)sem_t *sem = sem_open("/shared_sem", 0);// ... 使用 ...sem_close(sem);场景:
4. 单例模式
✅ 推荐:有名信号量 + O_EXCL 标志
sem_t *sem = sem_open("/app_singleton", O_CREAT | O_EXCL, 0666, 1);if (sem == SEM_FAILED) {if (errno == EEXIST) {printf("已有实例运行\n");exit(1); } perror("sem_open failed");exit(1);}// 程序主循环while (running) {// ...}// 退出前清理sem_close(sem);sem_unlink("/app_singleton");5. 清理注意事项
无名信号量:
sem_destroy() 销毁有名信号量:
每个 sem_open() 都要有对应的 sem_close()
通常在程序退出时调用 sem_unlink() 删除名称
如果程序异常退出,可能需要手动清理:
rm /dev/shm/sem.my_semaphore| 所有权 | ||
| 解锁线程 | sem_post() | |
| 计数功能 | ||
| 优先级继承 | ||
| 中断安全性 | ||
| 进程间支持 | ||
| 条件等待 | ||
| API 复杂度 | ||
| 范围 | ||
| 适用场景 |
详细说明:
所有权概念:
sem_post(),即使它从未 sem_wait() 过计数功能:
优先级继承:
PTHREAD_PRIO_INHERIT),可解决优先级反转进程间支持:
PTHREAD_PROCESS_SHARED),使用较少错误检测:
使用互斥锁(Mutex)的场景:
✅ 线程间互斥
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);// 临界区pthread_mutex_unlock(&mutex);✅ 需要优先级继承
pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);pthread_mutex_init(&mutex, &attr);✅ 需要错误检测
✅ 临界区保护
使用信号量(Semaphore)的场景:
✅ 进程间同步
// 有名信号量sem_t *sem = sem_open("/ipc_sem", O_CREAT, 0666, 1);✅ 需要计数功能(资源池)
// 数据库连接池sem_t db_connections;sem_init(&db_connections, 0, 10); // 10 个连接// 获取连接sem_wait(&db_connections);use_connection();sem_post(&db_connections);✅ 需要无所有权的解锁
✅ RTOS 中断同步
✅ 单例模式
sem_t *sem = sem_open("/singleton", O_CREAT | O_EXCL, 0666, 1);if (sem == SEM_FAILED && errno == EEXIST) {// 已有实例运行}总结对比:
sem_timedwait() | ||
“💡 核心原则:
线程间互斥:优先使用互斥锁( pthread_mutex_t)进程间同步:优先使用有名信号量( sem_open)资源计数:使用信号量 实时系统/中断:使用信号量
#include <glib.h>GThreadPool设计目标:
“✅ 安装开发包(Ubuntu/Debian):
sudo apt install libglib2.0-dev
g_thread_pool_new — 创建线程池GThreadPool* g_thread_pool_new( GFunc func, // 任务函数 gpointer user_data, // 共享数据(所有任务共用) gint max_threads, // 最大线程数(-1 = 无限制) gboolean exclusive, // 是否独占线程 GError **error // 错误信息(可为 NULL));参数说明:
func | void task_func(gpointer data, gpointer user_data) |
user_data | NULL |
max_threads | exclusive=TRUE,不能设为 -1 |
exclusive | TRUEmax_threads 个线程);FALSE:线程按需创建,可与其他非独占池共享全局线程 |
error | NULL 忽略 |
“📌
GFunc函数签名:voidtask_func(gpointer data, gpointer user_data);
data:每个任务独有的数据(由g_thread_pool_push传入);user_data:创建池时传入的共享数据。
g_thread_pool_push — 提交任务gboolean g_thread_pool_push( GThreadPool *pool, gpointer data, // 任务私有数据 GError **error);data 加入任务队列;TRUE 成功,FALSE 失败(如池已关闭);g_thread_pool_free — 释放线程池voidg_thread_pool_free( GThreadPool *pool, gboolean immediate, // TRUE:立即销毁,丢弃未处理任务 gboolean wait_ // TRUE:阻塞等待所有任务完成再返回);“⚠️ 注意:参数名是
wait_(带下划线),不是wait!
g_thread_pool_set_max_threads() | |
g_thread_pool_unprocessed() | |
g_thread_pool_move_to_front() | |
g_thread_pool_set_sort_function() |
适用场景对比表:
pthread_create 开销 | ||
具体应用场景:
GTK/GNOME 应用
Linux 后台服务/守护进程
多任务数据处理
需要资源控制的场景
不适用场景:
// glib_threadpool_basic.c#include<glib.h>#include<stdio.h>#include<stdlib.h>// 任务函数voidtask_func(gpointer data, gpointer user_data){int num = GPOINTER_TO_INT(data); // 转换 gpointer -> intint result = num * num; g_print("Task: %d^2 = %d (thread: %p)\n", num, result, g_thread_self()); g_usleep(500000); // 模拟耗时操作(0.5秒)}intmain(){ GError *error = NULL;// 创建线程池:最多 3 个线程,独占模式 GThreadPool *pool = g_thread_pool_new( task_func, // 任务函数NULL, // 无共享数据3, // max_threads TRUE, // exclusive &error );if (!pool) { g_printerr("Failed to create thread pool: %s\n", error->message);return1; }// 提交 6 个任务for (int i = 1; i <= 6; i++) { g_thread_pool_push(pool, GINT_TO_POINTER(i), NULL); }// 等待所有任务完成后释放 g_thread_pool_free(pool, FALSE, TRUE); g_print("All tasks completed.\n");return0;}编译运行:
gcc -o basic glib_threadpool_basic.c `pkg-config --cflags --libs glib-2.0`./basic输出示例:
Task: 1^2 = 1 (thread: 0x7f8a1c000b60)Task: 2^2 = 4 (thread: 0x7f8a1b7feb60)Task: 3^2 = 9 (thread: 0x7f8a1affeb60)Task: 4^2 = 16 (thread: 0x7f8a1c000b60)...All tasks completed.“✅ 观察:只有 3 个线程在复用,任务并发执行。
// glib_threadpool_shared.c#include<glib.h>#include<stdio.h>typedefstruct {char db_name[32];int conn_id;} DBContext;voiddb_task(gpointer data, gpointer user_data){int query_id = GPOINTER_TO_INT(data); DBContext *ctx = (DBContext*)user_data; g_print("Executing query %d on DB '%s' (conn=%d)\n", query_id, ctx->db_name, ctx->conn_id); g_usleep(300000); // 模拟查询}intmain(){ DBContext ctx = {.conn_id = 1001};strcpy(ctx.db_name, "users_db"); GThreadPool *pool = g_thread_pool_new(db_task, &ctx, 2, TRUE, NULL);for (int i = 1; i <= 4; i++) { g_thread_pool_push(pool, GINT_TO_POINTER(i), NULL); } g_thread_pool_free(pool, FALSE, TRUE);return0;}编译运行:
gcc -o shared glib_threadpool_shared.c `pkg-config --cflags --libs glib-2.0`./shared输出示例:
Executing query 1 on DB 'users_db' (conn=1001)Executing query 2 on DB 'users_db' (conn=1001)Executing query 3 on DB 'users_db' (conn=1001)Executing query 4 on DB 'users_db' (conn=1001)“✅ 所有任务共享同一个
DBContext,适合传递数据库连接、日志句柄等。
模式对比表:
| 独占(exclusive) | exclusive=TRUE | max_threads 个线程,不与其他池共享;适合高优先级、低延迟任务 | |
| 共享(non-exclusive) | exclusive=FALSE |
详细说明:
独占模式(exclusive=TRUE):
GThreadPool *pool = g_thread_pool_new(task_func, NULL, 3, TRUE, NULL);特点:
优点:
缺点:
适用场景:
共享模式(exclusive=FALSE):
GThreadPool *pool = g_thread_pool_new(task_func, NULL, -1, FALSE, NULL);特点:
优点:
缺点:
适用场景:
选择建议:
“💡 默认建议:
I/O 任务 → exclusive=FALSE实时任务 → exclusive=TRUE内存敏感 → exclusive=FALSE延迟敏感 → exclusive=TRUE
注意事项:
任务函数内避免长时间阻塞
// ❌ 不好的做法voidbad_task(gpointer data, gpointer user_data){while (1) { // 无限循环,占用线程 do_something(); }}// ✅ 好的做法voidgood_task(gpointer data, gpointer user_data){for (int i = 0; i < 100; i++) { // 有界循环 do_something();if (i % 10 == 0) { g_thread_yield(); // 主动让出 CPU } }}data 内存管理
// 提交时分配内存int *task_data = malloc(sizeof(int));*task_data = value;g_thread_pool_push(pool, task_data, NULL);// 任务函数中释放内存voidtask_func(gpointer data, gpointer user_data){int *value = (int*)data;// ... 处理数据 ...free(value); // 释放内存}不要在线程池任务中调用 g_thread_pool_free
// ❌ 危险!可能导致死锁voiddangerous_task(gpointer data, gpointer user_data){ g_thread_pool_free(pool, FALSE, TRUE); // 不要这样做!}错误处理
GError *error = NULL;GThreadPool *pool = g_thread_pool_new(task_func, NULL, 3, TRUE, &error);if (!pool) { g_printerr("Failed to create thread pool: %s\n", error->message); g_error_free(error);return1;}gboolean success = g_thread_pool_push(pool, data, &error);if (!success) { g_printerr("Failed to push task: %s\n", error->message); g_error_free(error);}GLib 线程初始化
// GLib ≥ 2.32 已自动初始化,无需手动调用// 但对于更早版本,需要:g_thread_init(NULL);最佳实践:
合理设置 max_threads
// 根据 CPU 核心数设置int max_threads = g_get_num_processors();GThreadPool *pool = g_thread_pool_new(task_func, NULL, max_threads, FALSE, NULL);使用独占模式处理实时任务
// 实时任务池:独占模式,固定线程数GThreadPool *realtime_pool = g_thread_pool_new( realtime_task, NULL, 2, TRUE, NULL);使用共享模式处理 I/O 任务
// I/O 任务池:共享模式,按需创建GThreadPool *io_pool = g_thread_pool_new( io_task, NULL, -1, FALSE, NULL);正确清理资源
// 等待所有任务完成再释放g_thread_pool_free(pool, FALSE, TRUE);// 不等待,立即丢弃未完成任务(谨慎使用)g_thread_pool_free(pool, TRUE, FALSE);监控任务队列状态
// 获取未处理任务数guint unprocessed = g_thread_pool_get_num_threads(pool);g_print("Unprocessed tasks: %u\n", unprocessed);// 获取当前线程数guint num_threads = g_thread_pool_get_num_threads(pool);g_print("Active threads: %u\n", num_threads);处理任务优先级
// 高优先级任务:移到队列头部g_thread_pool_push(pool, urgent_task, NULL);g_thread_pool_move_to_front(pool, urgent_task);使用共享数据传递上下文
typedefstruct {pthread_mutex_t *mutex;int *counter;} SharedContext;voidtask_func(gpointer data, gpointer user_data){ SharedContext *ctx = (SharedContext*)user_data; pthread_mutex_lock(ctx->mutex); (*ctx->counter)++; pthread_mutex_unlock(ctx->mutex);}处理大量任务时的内存管理
// 批量提交任务时,注意内存释放for (int i = 0; i < 1000; i++) { TaskData *data = create_task_data(i);if (!g_thread_pool_push(pool, data, NULL)) {free(data); // 推送失败时释放 }}性能优化建议:
避免频繁创建/销毁线程池
合理使用共享数据
监控线程池状态
处理错误和异常
考虑线程池的局限性
| 易用性 | |
| 性能 | |
| 资源控制 | |
| 生态集成 | |
| 适用语言 |
“🎯 推荐使用场景: GTK/GNOME 应用、Linux 后台服务、需要轻量级并发的 C 项目。
通过 GLib 线程池,你可以轻松写出高性能、可维护的并发 C 程序,而无需手动管理线程生命周期和同步原语。
man pthread_createman pthread_mutex_lockman pthread_rwlock_rdlockman sem_waitman pthread_cond_waitgcc -o program program.c -pthreadgcc -o sem_program sem_program.c -pthreadgcc -o named_sem named_sem.c -lrt -pthreadgcc -o glib_program glib_program.c `pkg-config --cflags --libs glib-2.0`总行数:约 2000+ 行
章节数:17+ 个主要章节
代码示例:20+ 个完整示例
核心知识点:
希望这份文档能帮助你深入理解 Linux 线程编程!