当前位置:首页>Linux>Linux 线程管理完全指南

Linux 线程管理完全指南

  • 2026-04-16 17:35:36
Linux 线程管理完全指南

Linux 线程管理完全指南


第一部分:基础概念

基础概念

在 Linux 系统中,进程和线程是程序执行的两个核心概念,理解它们对于掌握操作系统的工作原理至关重要。

简单来说,进程是资源分配的基本单位,而线程是CPU调度的基本单位。一个程序在运行时就是一个进程,而一个进程内部可以包含多个可以并发执行的线程。

什么是进程(Process)

进程是是程序的一个执行实例,是操作系统进行资源分配和管理的基本实体。你可以把它想象成一个正在运行的、拥有自己独立工作空间的"工厂"。

  • 独立资源:每个进程都拥有自己独立的虚拟地址空间、代码段、数据段、堆、栈、文件描述符等资源。
  • 相互隔离:进程之间是相互隔离的,一个进程的崩溃通常不会影响到其他进程,这提供了很高的稳定性和安全性。
  • 内核描述:在 Linux 内核中,进程由一个名为 task_struct 的数据结构(也称为进程控制块 PCB)来描述,它记录了进程的所有信息,如进程ID(PID)、状态、优先级、内存指针等。

什么是线程(Thread)

线程是进程内部的一个执行单元,是 CPU 调度和分派的基本单位。继续上面的比喻,线程就是"工厂"(进程)里的一条条"生产线"。

  • 资源共享:同一个进程内的所有线程共享该进程的地址空间、全局变量、文件描述符等资源。
  • 轻量级:每个线程拥有自己独立的栈空间和寄存器状态,但创建和切换的开销远小于进程。
  • Linux的实现:Linux 内核并不严格区分进程和线程,而是将它们统一视为"任务(task)"。线程通过 clone() 系统调用创建,在创建时指定共享部分资源(如内存空间),因此也被称为轻量级进程(LWP)

核心区别对比表

下表清晰地展示了进程与线程在几个关键维度上的差异:

对比维度
进程
线程
资源分配
资源分配的基本单位,拥有独立地址空间。
共享所属进程的资源,仅有独立的栈和寄存器。
调度单位
不是直接的调度单位。
CPU 调度的基本单位。
开销成本
创建、销毁和上下文切换的开销大。
创建、销毁和上下文切换的开销小。
通信方式
复杂,需使用管道、共享内存等进程间通信(IPC)机制。
简单,可直接读写进程的全局变量等共享数据。
崩溃影响
一个进程崩溃通常不影响其他进程。
一个线程崩溃可能导致整个进程退出。

适用场景

  • 进程:适用于需要高隔离性和稳定性的场景,例如运行不同的应用程序、服务器为每个客户端创建独立的进程来处理请求等。
  • 线程:适用于需要高并发和频繁通信的场景,例如图形用户界面(GUI)应用(一个线程处理界面,一个线程处理后台任务)、Web服务器(用线程池处理大量并发连接)等。

2. 第二部分:POSIX 线程基础

2.1 POSIX 线程简介

POSIX Threads(简称 pthreads)是 IEEE POSIX 1003.1c 标准定义的一套用于多线程编程的 C 语言 API。在 Linux 中,它由 libpthread 库实现(通常通过 -pthread 编译选项链接)。

所有 pthread 函数:


2.2 核心函数详解

2.2.1 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 属性无效)。

2.2.2 pthread_join — 等待线程结束(同步回收)

功能:阻塞调用线程,直到目标线程终止,并可获取其返回值。

intpthread_join(pthread_t thread, void **retval);

参数说明

  • thread:要等待的线程 ID。
  • retval:若非 NULL,则用于接收目标线程的返回值(即 pthread_exit() 或 return 的值)。

注意

  • 只能对 可连接(joinable) 线程调用;
  • 不能对同一个线程多次 join
  • 是实现线程同步的关键手段。

2.2.3 pthread_exit — 主动终止当前线程

功能:终止调用线程,并可返回一个值供其他线程通过 pthread_join 获取。

voidpthread_exit(void *retval);

参数说明

  • retval:线程退出时的返回值(void* 类型)。

注意

  • 线程函数 return xxx; 等价于 pthread_exit(xxx);
  • 主线程调用 pthread_exit 不会终止整个进程,子线程仍可运行。

2.2.4 pthread_detach — 设置线程为分离状态

功能:将线程标记为"分离(detached)",线程结束后自动释放资源,无需 pthread_join

intpthread_detach(pthread_t thread);

参数说明

  • thread:目标线程 ID。

返回值

  • 0:成功;
  • 非 0:错误(如 ESRCH 线程不存在,EINVAL 已是分离状态)。

关键点

  • 分离操作 不可逆
  • 分离线程 无法被 join,也无法获取其返回值;
  • 常用于"fire-and-forget"型任务。

✅ 推荐:创建后立即 pthread_detach(tid),避免资源泄漏。


2.2.5 pthread_cancel — 请求取消线程

功能:向目标线程发送"取消请求",但是否响应取决于线程的取消状态和类型。

intpthread_cancel(pthread_t thread);

参数说明

  • thread:目标线程 ID。

返回值

  • 0:请求成功入队;
  • 非 0:如 ESRCH(线程不存在)。

注意

  • 默认是 延迟取消(deferred cancellation),只在"取消点"(如 sleepreadwritepthread_testcancel)生效;
  • 线程可通过 pthread_setcancelstate() 禁用取消。

2.2.6 pthread_testcancel — 显式设置取消点

功能:如果当前有挂起的取消请求且线程允许取消,则在此处终止线程。

voidpthread_testcancel(void);

用途

  • 在长时间运行的计算循环中插入此函数,使线程能响应取消请求。

2.2.7 其他重要辅助函数

pthread_self() — 获取当前线程 ID
pthread_tpthread_self(void);
pthread_equal() — 比较两个线程 ID 是否相同
intpthread_equal(pthread_t t1, pthread_t t2)// 相等返回非0
pthread_setcancelstate() / pthread_setcanceltype()
  • 控制线程是否可被取消(PTHREAD_CANCEL_ENABLE/DISABLE
  • 控制取消类型(PTHREAD_CANCEL_DEFERRED/ASYNCHRONOUS

2.3 典型使用案例

案例 1:基本线程创建与 join

#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<string.h>voidworker(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

案例 2:分离线程(detached thread)

#include<stdio.h>#include<unistd.h>#include<pthread.h>voiddetached_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,程序仍能正常结束且无资源泄漏。


案例 3:线程取消(cancel + testcancel)

#include<stdio.h>#include<unistd.h>#include<pthread.h>voidcancellable_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;}

2.4 最佳实践总结

场景
推荐做法
需要获取线程结果
使用 pthread_join
不关心结果,只希望后台运行
创建后立即 pthread_detach
需要提前终止线程
使用 pthread_cancel + pthread_testcancel
避免僵尸线程
每个线程必须
 被 join 或 detach
多参数传递
将参数打包进 struct,传 struct*

⚠️ 重要提醒

  • 所有 pthread 函数 **不要依赖 errno**,必须检查返回值;
  • 编译时务必加 -pthread(不是 -lpthread,虽然效果类似,但 -pthread 更标准);
  • 线程安全:共享数据需用互斥锁(pthread_mutex_t)保护。

3. 第三部分:线程取消机制

3.1 pthread_setcanceltype 函数详解

函数原型与作用

#include<pthread.h>intpthread_setcanceltype(int type, int *oldtype);

功能

设置当前线程对取消请求的响应方式(即"何时被取消"),前提是该线程的取消状态为 PTHREAD_CANCEL_ENABLE(默认)。

参数说明

  • type:新取消类型,取值如下:

    • 异步取消:线程可能在任意指令处被立即终止,无需等待取消点。
    • 延迟取消:线程只在执行到"取消点"(如 sleepreadwritepthread_testcancel 等)时才会响应取消请求。
    • PTHREAD_CANCEL_DEFERRED默认
    • PTHREAD_CANCEL_ASYNCHRONOUS
  • oldtype:用于保存原来的取消类型(可为 NULL)。

返回值

  • 成功返回 0
  • 失败返回错误码(如 EINVAL 表示 type 无效)。

3.2 延迟取消 vs 异步取消

PTHREAD_CANCEL_DEFERRED(延迟)

  • 响应时机:仅在取消点响应
  • 安全性:✅ 高(可做清理)
  • 是否推荐:✅ 强烈推荐(默认)
  • 典型用途:正常任务取消

PTHREAD_CANCEL_ASYNCHRONOUS(异步)

  • 响应时机:可能在任意时刻被取消
  • 安全性:❌ 极低(资源状态未知)
  • 是否推荐:⚠️ 极少使用,仅限特殊场景
  • 典型用途:纯计算密集型死循环

关键警告

📌 异步取消非常危险!因为线程可能在 malloc 中间、持有锁时、修改全局数据结构时被杀死,导致:

  • 内存泄漏
  • 死锁(锁未释放)
  • 数据结构损坏
  • 程序崩溃

POSIX 标准只要求以下函数是"异步取消安全"的

  • pthread_cancel()
  • pthread_setcancelstate()
  • pthread_setcanceltype()

其他库函数(包括 printfmallocfree都不是异步取消安全的


3.3 异步取消的风险与安全场景

异步取消安全的函数

只有以下函数是 POSIX 标准要求的"异步取消安全"函数:

  • pthread_cancel()
  • pthread_setcancelstate()
  • pthread_setcanceltype()

唯一合理场景

线程处于一个纯计算、无任何系统调用、不访问共享资源、不分配内存的死循环中。

例如:

while (1) {    x = x * x + c; // 纯数学计算}

此时若用延迟取消,由于没有取消点,线程永远无法响应 pthread_cancel()。这时可临时启用异步取消。

危险示例

在以下代码中使用异步取消会导致严重问题:

// ❌ 危险!不要在实际代码中使用voiddangerous_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;}

⚠️ 后果:程序可能崩溃、内存泄漏、输出乱码,甚至死锁。


3.4 使用示例

示例 1:异步取消(谨慎使用)

// async_cancel.c#include<stdio.h>#include<unistd.h>#include<pthread.h>#include<signal.h>voidcompute_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;}

✅ 这个例子是安全的,因为线程只做纯计算,不涉及任何资源操作。


示例 2:异步取消 + 资源操作(危险!)

// dangerous_async.c (不要运行!仅作演示)#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>voiddangerous_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;}

⚠️ 后果:程序可能崩溃、内存泄漏、输出乱码,甚至死锁。


示例 3:用 pthread_testcancel() 更安全

voidsafe_compute(void* arg){volatiledouble x = 1.0;while (1) {        x = x * x + 0.1;        pthread_testcancel(); // 显式取消点,安全!    }returnNULL;}

这种方式既能让线程响应取消,又保持了高安全性,强烈推荐


3.5 最佳实践建议

  1. **永远优先使用 PTHREAD_CANCEL_DEFERRED**(默认);
  2. 如果必须取消纯计算线程:
    • 在循环中定期插入 pthread_testcancel()(推荐);
    • 或者临时切换为异步取消,但确保循环内绝对干净
  3. 禁止在以下代码中使用异步取消:
    • 操作堆内存(malloc/free
    • 操作文件/网络(read/write
    • 使用互斥锁/信号量
    • 调用任何非异步安全的库函数

4. 第四部分:同步原语

4.1 互斥锁(Mutex)

4.1.1 基本用法

互斥锁(Mutex)是最基本的同步原语,用于保护临界区,确保同一时刻只有一个线程能访问共享资源。

pthread_mutex_init — 初始化互斥锁
intpthread_mutex_init(pthread_mutex_t *mutex,constpthread_mutexattr_t *attr);

参数说明

  • mutex:指向互斥锁变量的指针。
  • attr:互斥锁属性对象,可为 NULL(使用默认属性)。

返回值

  • 成功:返回 0
  • 失败:返回错误码
    • EAGAIN:系统资源不足
    • EINVAL:属性值无效
    • ENOMEM:内存不足

注意

  • 已初始化的互斥锁不能再次初始化
  • 静态初始化可用:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock — 加锁
intpthread_mutex_lock(pthread_mutex_t *mutex);

参数说明

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码
    • EDEADLK:检测到死锁(某些实现)
    • EINVAL:mutex 无效

行为

  • 如果锁未被持有,则立即获取锁
  • 如果锁已被其他线程持有,则当前线程阻塞,直到锁可用
  • 如果锁已被当前线程持有,则导致死锁(除非是递归锁)

pthread_mutex_unlock — 解锁
intpthread_mutex_unlock(pthread_mutex_t *mutex);

参数说明

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码
    • EPERM:当前线程未持有该锁
    • EINVAL:mutex 无效

行为

  • 释放互斥锁
  • 如果有线程在等待该锁,则唤醒其中一个线程
  • 只有持有锁的线程才能解锁,其他线程解锁会失败

pthread_mutex_trylock — 尝试加锁(非阻塞)
intpthread_mutex_trylock(pthread_mutex_t *mutex);

参数说明

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码
    • EBUSY:锁已被其他线程持有
    • EINVAL:mutex 无效

行为

  • 如果锁未被持有,则立即获取锁并返回 0
  • 如果锁已被持有,则立即返回EBUSY,不阻塞
  • 适用于需要避免阻塞的场景

pthread_mutex_destroy — 销毁互斥锁
intpthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明

  • mutex:指向互斥锁变量的指针。

返回值

  • 成功:返回 0
  • 失败:返回错误码
    • EBUSY:锁当前被某个线程持有
    • EINVAL:mutex 无效

注意

  • 销毁前必须确保没有线程持有该锁
  • 销毁后互斥锁变量不可再使用,除非重新初始化
  • 静态初始化的锁也需要销毁(虽然不是必须的)

典型使用模式

#include<pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int shared_data = 0;voidthread_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;}

关键点

  1. 加锁和解锁必须成对出现
  2. 同一线程多次加锁会导致死锁(除非是递归锁)
  3. 已加锁的线程必须自己解锁,其他线程无法解锁
  4. 销毁锁前确保无线程持有
  5. 静态初始化的锁也需要适当清理

4.1.2 优先级反转问题

经典优先级反转问题

最终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 一直等下去。

详细执行流程(时间线)

假设系统采用 抢占式优先级调度(高优先级可打断低优先级):

时间
发生事件
谁在运行?
关键状态
T0
L 开始运行
(当前无更高优先级任务)
L
L 获取互斥锁,进入临界区(持有资源 R)
T1
H 就绪
(例如被中断唤醒)
→ 调度器切换
H 尝试获取锁,发现已被 L 持有 → H 被阻塞(进入等待队列) L 恢复运行(因为 H 被阻塞了,不参与调度)
T2
M 就绪
(例如定时器到期)
→ 调度器切换
M 优先级(中) > L 优先级(低) → M 抢占 LL 被挂起,但仍然持有互斥锁和资源 R
T3 ~ Tn
M 持续运行
(例如做大量计算)
M
H 仍在阻塞等待L 被挂起,无法继续执行,更无法释放锁
Tn+1
M 主动放弃 CPU
(例如 sleep 或结束)
→ 调度器切换
L 恢复运行(因为它是当前最高优先级的就绪任务)
Tn+2
L 完成临界区,释放互斥锁
L
锁释放后,H 被唤醒
Tn+3
H 运行
(优先级最高)
H
H 获取锁,访问资源 R
为什么说这是"优先级反转"
  • 理想情况:在抢占式调度下,最高优先级的就绪任务应该立即运行

  • 实际情况

    • H 是最高优先级,但它被阻塞了(这是合理的,因为要等资源);
    • 但 H 的阻塞时间不仅取决于 L 的临界区时间,还被 M 的执行时间无限延长
    • M 的优先级低于 H,却间接阻止了 H 的执行

💡 本质高优先级任务 H 的执行,被一个优先级比它低的任务 M 所延迟。这颠倒了优先级调度的本意——这就是"反转"的含义。

关键结论
  1. M 没有、也不需要访问资源 R,它只是"路过"的普通任务;
  2. 问题根源在于:
    • 资源共享(L 和 H 竞争争 R);
    • 抢占式调度(M 可以打断 L);
    • 普通互斥锁不感知优先级(L 持有锁时,优先级不会提升)。
  3. 结果:H 的响应时间变得不可预测且可能很长(取决于 M 的行为),这在实时系统中是灾难性的(如火星探路者事故)。
解决方案回顾

引入优先级继承互斥锁

  • 当 H 因等待 L 持有的锁而阻塞时,L 的优先级被临时提升到 H 的级别
  • 此时 M(中优先级)无法再抢占 L
  • L 快速完成临界区,释放锁;
  • H 立即获得锁,继续执行。

这样,H 的阻塞时间 = L 的临界区时间(有界),不再受 M 影响。


4.2 读写锁(Read-Write Lock)

4.2.1 读写锁解决了什么问题

在多线程程序中,如果对共享资源的访问是 "读多写少" 的模式(例如缓存、配置表、数据库索引),使用普通互斥锁会限制并发读取的能力——即使多个线程只是读数据,也必须串行执行。

✅ 读写锁的核心思想

  • 允许多个读者同时读'(读-读不互斥);
  • 写操作必须独占(写-写、写-读互斥);
  • 提升并发性能,尤其在读密集型场景。

📌 本质:区分读/写操作,精细化控制并发粒度


4.2.2 POSIX 读写锁核心函数列表

所有函数定义在 <pthread.h> 中,需链接 -pthread

函数
功能
pthread_rwlock_init
初始化读写锁
pthread_rwlock_destroy
销毁读写锁
pthread_rwlock_rdlock
获取读锁(阻塞)
pthread_rwlock_tryrdlock
尝试获取读锁(非阻塞)
pthread_rwlock_wrlock
获取写锁(阻塞)
pthread_rwlock_trywrlock
尝试获取写锁(非阻塞)
pthread_rwlock_unlock
释放读锁或写锁

4.2.3 函数声明与参数详解

初始化与销毁
intpthread_rwlock_init(pthread_rwlock_t *rwlock,constpthread_rwlockattr_t *attr);
  • rwlock:指向读写锁变量的指针。
  • attr:属性对象(可为 NULL 表示默认属性)。
  • 返回值:0 成功,非 0 错误码。
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);
  • 无论是读锁还是写锁,都用此函数释放;
  • 内核会自动判断当前是哪种锁并做相应处理。

4.2.4 读写锁适用场景

场景 1:缓存系统(读多写少)
  • 多个线程频繁查询缓存(读);
  • 少数线程更新缓存(写);
  • 使用读写锁可让多个查询并发执行。
场景 2:配置文件热加载
  • 应用程序主线程读取配置(高频);
  • 后台线程监听配置变更并更新(低频);
  • 读写锁避免每次读都加互斥锁。
场景 3:数据库索引结构
  • 查询操作(读)远多于插入/删除(写);
  • 读写锁提升查询吞吐量。

4.2.5 案例演示

案例 1:基本读写锁使用
#include<stdio.h>#include<unistd.h>#include<pthread.h>pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;int shared_data = 0;voidreader(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;}voidwriter(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 完成。


4.2.6 写饥饿(Writer Starvation)问题

什么是写饥饿
  • 持续有新读者到来时,写线程可能永远无法获得写锁
  • 即使写线程早已在等待,但新读者不断"插队",导致写者无限期阻塞。
为什么会发生
  1. 默认策略偏向读者(reader-preference)

    • Linux/glibc 默认实现中,当一个读者释放锁后,如果有新读者在等待,会优先唤醒读者;
    • 写者只能等所有当前读者 + 新来的读者都完成才能执行。
  2. 读者持有锁时间长(如 sleep(1));

  3. 高并发读请求源源不断。

⚠️ 后果:写操作延迟极大,甚至系统"卡死"。


4.2.7 如何避免写饥饿

方法 1:使用"写优先"策略(Linux 特有)

通过设置读写锁属性:

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 顺序执行,避免饥饿。
方法 2:限制读者数量或持有时间
  • 在应用层控制并发读者数;
  • 避免在读锁内执行耗时操作(如 I/O、sleep)。
方法 3:使用更高级同步原语
  • 如 Linux 内核中的 rw_semaphore(读写信号量)天然支持写者优先;
  • 或使用 RCU(Read-Copy-Update)机制(适用于特定场景)。

4.2.8 会有"读饥饿"吗

理论上可能,但实践中极其罕见

为什么?

  • 写操作是独占的,一次只能一个写者;
  • 即使有大量写者排队,每个写者执行完后,所有等待的读者可以并发执行
  • 除非写者执行频率极高且持有锁时间很长,否则读者不会长期等待。

📌 结论:写饥饿是主要问题,读饥饿几乎无需担心


4.2.9 总结对比表

特性
互斥锁(Mutex)
读写锁(RWLock)
读-读并发
❌ 不允许
✅ 允许
读-写并发
❌ 不允许
❌ 不允许
写-写并发
❌ 不允许
❌ 不允许
适用场景
通用
读多写少
性能优势
高并发读时显著
主要风险
死锁
写饥饿

4.3 自旋锁(Spinlock)

4.3.1 POSIX 自旋锁的核心函数

所有函数定义在 <pthread.h> 中,需链接 -pthread

函数
功能
pthread_spin_init
初始化自旋锁
pthread_spin_destroy
销毁自旋锁
pthread_spin_lock
获取自旋锁(忙等待)
pthread_spin_trylock
尝试获取自旋锁(非阻塞)
pthread_spin_unlock
释放自旋锁

⚠️ 注意:POSIX 自旋锁仅适用于 SMP(多核)系统。在单核系统上,自旋会导致死锁(因为持有锁的线程无法运行)。


4.3.2 函数声明与参数说明

初始化
intpthread_spin_init(pthread_spinlock_t *lock, int pshared);
  • lock:指向自旋锁变量的指针。
  • pshared
    • 0:仅限同一进程内线程使用(最常见);
    • 非 0:可用于进程间共享(需放在共享内存中,如 mmap 或 shm_open)。
  • 返回值:0 成功,非 0 错误码(如 ENOMEM)。
销毁
intpthread_spin_destroy(pthread_spinlock_t *lock);
  • 销毁前必须确保锁处于未锁定状态
加锁(忙等待)
intpthread_spin_lock(pthread_spinlock_t *lock);
  • 若锁空闲 → 立即获取;
  • 若锁被占用 → 持续循环检查(自旋),直到锁释放;
  • 不会让出 CPU,始终占用一个核心。
尝试加锁(非阻塞)
intpthread_spin_trylock(pthread_spinlock_t *lock);
  • 成功返回 0,失败(已被占用)返回 EBUSY
解锁
intpthread_spin_unlock(pthread_spinlock_t *lock);
  • 释放锁,唤醒(实际上是允许)其他自旋线程获取。

4.3.3 底层实现原理(简要)

虽然 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)。

🔍 关键点:整个"检查+设置"操作是原子的,防止多个线程同时认为锁空闲。


4.3.4 优点(Advantages)

优点
说明
✅ 极低延迟
无上下文切换、无系统调用,适合纳秒级临界区
✅ 高吞吐(低竞争时)
多核下,锁释放后立即可被另一个核心获取
✅ 简单高效
内存开销小(通常就是一个 int),实现简洁
✅ 避免调度开销
不涉及内核调度器,适合实时性要求高的场景

4.3.5 缺点(Disadvantages)

缺点
说明
❌ 浪费 CPU 资源
自旋期间持续占用 CPU,不做有用功
❌ 可能导致优先级反转
高优先级线程自旋等待低优先级线程释放锁
❌ 单核系统上无效甚至死锁
自旋线程阻止持有锁的线程运行
❌ 不适合长临界区
若临界区 > 几微秒,互斥锁更优
❌ 不可递归
同一线程重复加锁会导致死锁(自旋等自己)

4.3.6 典型使用场景

  • 保护极短的临界区(如修改一个原子计数器、更新指针);
  • 高性能计算/实时系统中对延迟极度敏感的路径;
  • 作为更复杂锁的底层构建块(如实现读写锁、队列锁);
  • 内核或驱动开发(但注意:POSIX 自旋锁是用户态的,内核有自己的一套)。

📌 经验法则:临界区执行时间 < 1 微秒 时考虑自旋锁;否则用互斥锁。


4.3.7 使用示例

#include<stdio.h>#include<pthread.h>#include<unistd.h>pthread_spinlock_t spinlock;volatileint counter = 0;voidworker(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


4.3.8 与互斥锁(Mutex)对比

特性
自旋锁(Spinlock)
互斥锁(Mutex)
等待方式
忙等待(占用 CPU)
睡眠(让出 CPU)
适用临界区
极短(纳秒~微秒)
任意长度
上下文切换
单核系统
不适用(死锁风险)
安全
CPU 利用率
高(等待时)
低(等待时)
实现层级
用户态(POSIX)或内核态
通常涉及内核

**POSIX 自旋锁是一把"双刃剑"**:

  • ✅ 用对了:极致性能,超低延迟;
  • ❌ 用错了:CPU 空转,系统卡顿。

建议:除非你明确知道临界区非常短且运行在多核系统上,否则优先使用 pthread_mutex_t。自旋锁更适合底层库开发者或性能调优专家使用。


5. 第五部分:线程间通信

5.1 条件变量(Condition Variables)

5.1.1 条件变量解决了什么问题

互斥锁(Mutex)可以保护共享数据不被并发破坏,但它**无法解决"等待某个条件成立"**的问题。

典型困境

"我想在 buffer_not_empty 为真时才从缓冲区取数据,但我不知道什么时候它会变真。"

如果用轮询(busy-waiting):

while (buffer_empty) {// 空转,浪费 CPU}

效率极低!

条件变量的作用
  • 允许线程原子地释放锁并进入睡眠,等待某个条件成立;
  • 当其他线程改变条件后,可唤醒等待线程
  • 避免无效轮询,实现高效线程间通信与同步。

🔑 核心思想:**"等待 + 通知"机制**,必须与互斥锁配合使用。


5.1.2 POSIX 条件变量核心函数列表

所有函数定义在 <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;

5.1.3 函数声明与参数详解

初始化与销毁
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);

⚠️ 原子性操作(这是精髓!)

  1. **自动释放 mutex**;
  2. 将当前线程加入 cond 的等待队列并阻塞
  3. 当被唤醒后,**在返回前重新获取 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);   // 唤醒所有
  • 若无线程等待,则无任何效果
  • 通常在修改了共享状态后调用。

5.1.4 正确使用模式(必须牢记)

pthread_mutex_lock(&mutex);// 必须用 while 循环检查条件!(防虚假唤醒)while (condition_is_false) {    pthread_cond_wait(&cond, &mutex);}// 此时条件成立,操作共享数据do_something();pthread_mutex_unlock(&mutex);
为什么用 while 而不是 if
  • 虚假唤醒(Spurious Wakeup):即使无人调用 signal,线程也可能被唤醒(POSIX 允许);
  • 多消费者竞争:多个线程被 broadcast 唤醒,但条件可能只满足一个。

✅ 永远用 while 检查条件!


5.1.5 适用场景

场景
说明
✅ 生产者-消费者模型
缓冲区满/空时等待
✅ 线程池任务队列
工作者线程等待新任务
✅ 屏障(Barrier)同步
多线程等待全部到达某点
✅ 事件驱动系统
等待特定事件发生

5.1.6 经典案例详解

案例 1:生产者-消费者(有界缓冲区)
#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;  // 缓冲区不空voidproducer(void* arg){for (int i = 0; i < 20; i++) {        pthread_mutex_lock(&mutex);// 缓冲区满?等待while (count == BUFFER_SIZE) {            pthread_cond_wait(&not_full, &mutex);        }        buffer[in] = i;        in = (in + 1) % BUFFER_SIZE;        count++;printf("Produced: %d\n", i);        pthread_cond_signal(&not_empty); // 通知消费者        pthread_mutex_unlock(&mutex);        usleep(100000); // 模拟生产耗时    }returnNULL;}voidconsumer(void* arg){for (int i = 0; i < 20; i++) {        pthread_mutex_lock(&mutex);// 缓冲区空?等待while (count == 0) {            pthread_cond_wait(&not_empty, &mutex);        }int item = buffer[out];        out = (out + 1) % BUFFER_SIZE;        count--;printf("Consumed: %d\n", item);        pthread_cond_signal(&not_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 唤醒对方角色。

案例 2:带超时的等待(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;voidwaiter(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;}voidsetter(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)


5.1.7 重要注意事项

  1. 必须与互斥锁配合使用
  2. wait 前必须持有锁
  3. 永远用 while 检查条件
  4. signal/broadcast 可在锁内或锁外调用(通常在锁内,保证状态一致性);
  5. 条件变量本身不存储状态,它只是"信使",真正的状态由共享变量表示;
  6. 避免在信号处理函数中调用(非异步信号安全)。

5.1.8 总结

特性
说明
核心价值
实现高效的"等待-通知"同步
关键函数wait
signalbroadcast
必须搭配
互斥锁 + 共享状态变量
经典模式while (!condition) wait(...)
典型应用
生产者-消费者、任务队列、事件等待

💡 记住:条件变量不是"条件",而是"等待条件变化的机制"。真正的条件由你的共享变量定义。

通过合理使用 POSIX 条件变量,你可以构建高效、可扩展的多线程协作程序,避免轮询带来的资源浪费。


5.2 信号量(Semaphore)

5.2.1 信号量的本质

信号量(Semaphore)本质上是一个非负整数计数器,配合两个原子操作实现线程/进程间的同步机制:

  • P 操作(sem_wait:申请资源,计数器减 1;若值为 0 则阻塞。
  • V 操作(sem_post:释放资源,计数器加 1;若有等待者则唤醒一个。

所有操作都是原子的,由操作系统或硬件保证,不会被中断。


5.2.2 计数功能 vs 互斥功能

计数功能(Counting Semaphore)

信号量的初始值)> 1(如 3、5、10),表示可用资源的数量

典型场景:

场景
信号量初始值
含义
数据库连接池
10
最多允许 10 个线程同时使用连接
打印机队列
3
系统有 3 台打印机,可并发打印
生产者-消费者缓冲区
BUFFER_SIZE
表示"空槽"数量

特点:允许多个任务同时成功获取信号量(只要计数器 > 0)。

互斥功能(Binary Semaphore)

信号量的初始值 = 1,此时它退化为一个二值信号量(Binary Semaphore),实现互斥访问

特点:任意时刻最多只有一个线程能持有该信号量。

与 Mutex 的关键区别
特性
计数信号量(Counting)
二值信号量(互斥)
初始值
≥ 1(通常 >1)
= 1
用途
管理多个同类资源
实现互斥访问
并发性
允许多个任务同时进入
仅允许一个任务进入
类比
停车场有 10 个车位
厕所只有一个隔间
是否等同于 Mutex?
❌ 不完全等同
⚠️ 功能相似,但无所有权/优先级继承

⚠️ 重要区别虽然二值信号量可以实现互斥,但它不是互斥锁(Mutex)

  • **Mutex 有"所有权"**:只有加锁的线程能解锁;
  • 信号量无所有权:任何线程都可以 sem_post(即使它没 wait 过);
  • Mutex 支持优先级继承,信号量不支持。

5.2.3 无名信号量(Unnamed Semaphore)

5.2.3.1 定义与特点

无名信号量(Unnamed Semaphore),也称内存信号量,具有以下特点:

  • 存在于进程的内存空间中;
  • 仅对初始化它的进程及其子进程可见;
  • 通过 sem_init() 初始化,sem_destroy() 销毁;
  • 适用于:
    • 线程间同步(pshared=0,最常见)
    • 共享内存中的进程间同步(pshared≠0,高级用法)

📌 本质:无名信号量是一个 sem_t 类型的变量,存在于进程内存中。


5.2.3.2 核心函数详解
sem_init — 初始化无名信号量
#include<semaphore.h>intsem_init(sem_t *sem, int pshared, unsignedint value);

参数说明

  • sem:指向信号量变量的指针。
  • pshared:共享标志
    • 0:仅限同一进程内的线程共享(最常见)
    • 非 0:可在不同进程间共享(需配合共享内存,如 mmap 或 shm_open
  • value:信号量的初始值(非负整数)

返回值

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno
    • EINVAL:value 超过 SEM_VALUE_MAX
    • ENOSYS:系统不支持 pshared 非 0

⚠️ 注意

  • 信号量必须在使用前初始化
  • 同一信号量只能初始化一次

sem_destroy — 销毁无名信号量
intsem_destroy(sem_t *sem);

参数说明

  • sem:指向信号量变量的指针

返回值

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno
    • EINVAL:sem 不是有效的信号量
    • EBUSY:有进程/线程正在等待该信号量

⚠️ 注意

  • 销毁前确保没有任何线程正在等待该信号量
  • 销毁后该信号量变量不可再使用,除非重新初始化

sem_wait / sem_trywait — 等待/获取信号量
intsem_wait(sem_t *sem);           // 阻塞等待intsem_trywait(sem_t *sem);        // 非阻塞尝试

sem_wait 行为

  • 如果信号量值 > 0:
    • 值减 1
    • 函数立即返回
  • 如果信号量值 == 0:
    • 调用线程阻塞,直到值 > 0
    • 值减 1
    • 函数返回

sem_trywait 行为

  • 如果信号量值 > 0:
    • 值减 1
    • 返回 0
  • 如果信号量值 == 0:
    • 不阻塞,立即返回
    • 返回 -1,设置 errno = EAGAIN

返回值

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • EAGAIN(仅 sem_trywait):信号量值为 0
    • EINTR:调用被信号中断
    • EINVAL:sem 不是有效的信号量

sem_post — 释放信号量
intsem_post(sem_t *sem);

行为

  • 信号量值加 1
  • 如果有进程/线程正在等待该信号量:
    • 唤醒其中一个等待者(FIFO 或优先级,取决于实现)
    • 被唤醒的线程将竞争获取信号量

返回值

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • EINVAL:sem 不是有效的信号量
    • EOVERFLOW:信号量值超过 SEM_VALUE_MAX

⚠️ 重要:此函数是异步信号安全的(async-signal-safe),可以在信号处理函数中调用。


5.2.3.3 使用场景

场景 1:线程间同步(最常见)

  • 多个线程之间协调执行顺序
  • 生产者-消费者模型
  • 工作线程池

场景 2:进程间同步(高级用法)

  • 需要 pshared=1 + 共享内存
  • 父子进程之间共享信号量
  • 性能优于有名信号量(避免文件系统操作)

5.2.3.4 完整代码示例
示例 1:无名信号量 - 线程间同步
// unnamed_sem_thread.c#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<semaphore.h>#include<unistd.h>sem_t sem;voidconsumer(void* arg){printf("消费者: 等待信号...\n");    sem_wait(&sem);  // 等待生产者发送信号printf("消费者: 收到信号,开始处理\n");returnNULL;}voidproducer(void* arg){    sleep(2);  // 模拟生产耗时printf("生产者: 发送信号\n");    sem_post(&sem);  // 唤醒消费者returnNULL;}intmain(){// 初始化无名信号量,初始值 0(消费者开始时需等待)if (sem_init(&sem, 00) == -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 处阻塞直到收到信号。


示例 2:无名信号量 - 进程间同步(共享内存)
// 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, 10) == -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

5.2.4 有名信号量(Named Semaphore)

5.2.4.1 定义与特点

有名信号量(Named Semaphore),也称文件系统信号量,具有以下特点:

  • 名字标识,存在于文件系统中(Linux 通常在 /dev/shm 目录)
  • 可被不相关的进程访问(无需 fork 关系)
  • 通过 sem_open() 打开/创建,sem_close() 关闭,sem_unlink() 删除
  • 持久性:即使所有进程都关闭了信号量,它仍然存在于文件系统中(需要手动 sem_unlink 删除)
  • 适用于:
    • 独立进程间同步(如守护进程与客户端进程)
    • 跨会话/跨用户的进程同步(权限允许)
    • 单例模式实现(确保只有一个程序实例运行)

📌 本质:有名信号量是文件系统中的一个命名对象,类似于文件,但由内核管理。


5.2.4.2 命名规则与文件系统位置

命名规则

  • 名称格式:必须以 / 开头(POSIX 标准)
    • ✅ 有效:"/mysem""/app_sem_1""/database_lock"
    • ❌ 无效:"mysem""./sem""sem_name"
  • 名称只能包含一个 / 开头,后续不能再有 /
  • 名称长度:受 NAME_MAX 限制(通常为 255 字符)
  • 名称中可包含字母、数字、下划线等

文件系统位置

  • Linux:通常位于 /dev/shm/(tmpfs 临时文件系统)
    • 可以使用 ls /dev/shm/ 查看所有有名信号量
  • 其他 Unix:实现可能不同,但行为一致

示例

# 创建名为 "/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")。


5.2.4.3 核心函数详解
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:权限模式(八进制,如 06660600
    • unsigned int value:信号量初始值

返回值

  • 成功:返回指向 sem_t 的指针
  • 失败:返回 SEM_FAILED(相当于 (sem_t *)-1),并设置 errno
    • EEXISTO_CREAT | O_EXCL 指定,但信号量已存在
    • ENOENT:信号量不存在(且未指定 O_CREAT
    • EACCES:权限不足
    • ENAMETOOLONG:名称过长
    • EINVAL:名称无效

示例

// 创建名为 "/mysem" 的有名信号量,初始值 1sem_t *sem = sem_open("/mysem", O_CREAT, 06661);if (sem == SEM_FAILED) {    perror("sem_open failed");exit(1);}

✅ 最佳实践:始终检查返回值是否为 SEM_FAILED,而不是 NULL


sem_close — 关闭有名信号量
intsem_close(sem_t *sem);

行为

  • 关闭当前进程对信号量的引用
  • 减少引用计数(每个 sem_open 调用对应一个引用)

返回值

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • EINVAL:sem 不是有效的信号量

⚠️ 重要区别

  • sem_close()不会删除信号量,只是关闭当前进程的句柄
  • 信号量在所有进程都关闭后仍然存在(除非已调用 sem_unlink
  • 每个 sem_open() 调用都应该有对应的 sem_close() 调用

sem_unlink — 删除有名信号量
intsem_unlink(constchar *name);

行为

  • 立即删除信号量的名称(从文件系统中移除)
  • 如果有其他进程正在使用该信号量,它们可以继续使用
  • 所有进程都关闭该信号量后,信号量资源才会被真正释放

返回值

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • ENOENT:信号量不存在
    • EACCES:权限不足
    • ENAMETOOLONG:名称过长

⚠️ 典型用法

// 创建进程(守护进程)sem_t *sem = sem_open("/mysem", O_CREAT, 06661);// ... 使用信号量 ...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 节。


5.2.4.4 使用场景

场景 1:守护进程与客户端进程同步

  • 守护进程(Daemon)创建有名信号量,提供服务
  • 多个客户端进程通过打开同一个有名信号量来请求服务
  • 即使守护进程重启,客户端仍可连接(如果信号量未被删除)

场景 2:多个独立服务实例协调

  • 多个独立运行的进程实例需要协调共享资源
  • 使用有名信号量实现跨进程的互斥访问
  • 不需要进程间有 fork 关系

场景 3:单例模式实现

  • 确保系统中只有一个程序实例在运行
  • 使用 O_CREAT | O_EXCL 标志尝试创建有名信号量
  • 如果创建失败(errno == EEXIST),说明已有实例运行
  • 实例退出时调用 sem_unlink() 删除信号量

5.2.4.5 完整代码示例
示例 3:有名信号量 - 多进程同步(守护进程 + 客户端)
// 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, 06661);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() 在守护进程退出时调用(可选)

示例 4:有名信号量 - 单例模式实现
// 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, 06661);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

5.2.5 无名信号量 vs 有名信号量对比

对比表格
对比维度
无名信号量
有名信号量
存储位置
进程内存空间
文件系统(通常在 /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

详细说明

  1. 存储位置与生命周期

    • 无名信号量:存在于进程内存中,随进程/线程销毁而消失,无需手动清理
    • 有名信号量:存在于文件系统,即使所有进程退出后仍存在,需要显式 sem_unlink() 删除
  2. 可见范围

    • 无名信号量:仅对初始化它的进程及其子进程可见(进程内或共享内存)
    • 有名信号量:系统中任何有权限的进程都可访问
  3. 进程间支持

    • 无名信号量:需要 pshared=1 + 共享内存(mmap 或 shm_open),实现较复杂
    • 有名信号量:原生支持进程间同步,无需共享内存
  4. 性能

    • 无名信号量:完全在内存中操作,性能最优
    • 有名信号量:涉及文件系统操作(创建/打开),性能略低
  5. 初始化与清理

    • 无名信号量sem_init() → 使用 → sem_destroy()
    • 有名信号量sem_open() → 使用 → sem_close() → sem_unlink()
  6. 错误处理

    • 无名信号量:错误通过返回值 -1 + errno 表示
    • 有名信号量sem_open() 失败返回 SEM_FAILED(不是 NULL),其他类似

5.2.6 选择建议

决策树

问题 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, 01);  // 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(NULLsizeof(sem_t), PROT_READ | PROT_WRITE,                   MAP)SHARE, shm_fd, 0);// 初始化,pshared=1sem_init(sem, 11);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, 06661);// ... 使用 ...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, 06661);// ... 使用 ...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, 06661);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() 销毁
  • 避免重复初始化或销毁
  • 进程退出时自动清理(即使忘记 destroy)

有名信号量

  • 每个 sem_open() 都要有对应的 sem_close()

  • 通常在程序退出时调用 sem_unlink() 删除名称

  • 如果程序异常退出,可能需要手动清理:

    rm /dev/shm/sem.my_semaphore

5.2.7 信号量 vs 互斥锁

关键区别表格
特性
信号量
互斥锁
所有权
无所有权
有所有权
解锁线程
任意线程都可以 sem_post()
必须是加锁的线程才能解锁
计数功能
支持(可以是任意非负整数)
仅支持二值(锁定/未锁定)
优先级继承
不支持
支持(可配置)
中断安全性
可用于中断(异步信号安全)
不可用于中断
进程间支持
支持(有名信号量或共享内存)
仅 POSIX 进程共享 mutex
条件等待
可实现超时等待
原生不支持
API 复杂度
中等
简单
范围
计数(资源池)或互斥
仅互斥
适用场景
资源计数、进程同步、中断同步
线程互斥、临界区保护

详细说明

  1. 所有权概念

    • 信号量:无所有权,任何线程都可以 sem_post(),即使它从未 sem_wait() 过
    • 互斥锁:有所有权,只有加锁的线程才能解锁,避免误释放
  2. 计数功能

    • 信号量:可以是任意非负整数(0, 1, 2, ...),适合管理多个资源
    • 互斥锁:只有两种状态(锁定/未锁定),无法计数
  3. 优先级继承

    • 信号量:不支持,可能导致优先级反转问题
    • 互斥锁:支持(通过 PTHREAD_PRIO_INHERIT),可解决优先级反转
  4. 进程间支持

    • 有名信号量:原生支持进程间同步
    • 无名信号量:通过共享内存支持
    • 信号量
    • 互斥锁:仅支持进程共享 mutex(PTHREAD_PROCESS_SHARED),使用较少
  5. 错误检测

    • 信号量:可能导致逻辑错误(如错误的线程解锁)难以检测
    • 互斥锁:可检测到"非拥有者解锁"错误(部分实现)

选择指南

使用互斥锁(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, 06661);

✅ 需要计数功能(资源池)

// 数据库连接池sem_t db_connections;sem_init(&db_connections, 010);  // 10 个连接// 获取连接sem_wait(&db_connections);use_connection();sem_post(&db_connections);

✅ 需要无所有权的解锁

  • 某些场景下,不同线程需要释放信号量
  • 例如:生产者-消费者,生产者发送信号,消费者等待

✅ RTOS 中断同步

  • 信号量是异步信号安全的,可在中断处理函数中使用
  • 互斥锁不可在中断中使用

✅ 单例模式

sem_t *sem = sem_open("/singleton", O_CREAT | O_EXCL, 06661);if (sem == SEM_FAILED && errno == EEXIST) {// 已有实例运行}

总结对比

场景
推荐选择
原因
线程间互斥
✅ 互斥锁
有所有权、支持优先级继承、简单高效
需要优先级继承
✅ 互斥锁
信号量不支持
资源计数(如连接池)
✅ 信号量
支持计数功能
进程间同步(独立进程)
✅ 有名信号量
原生支持,无需共享内存
进程间同步(父子进程)
⚖️ 信号量或互斥锁
信号量更常用,互斥锁也可
单例模式
✅ 有名信号量
命名机制天然适合单例
RTOS 中断同步
✅ 信号量
异步信号安全
需要超时等待
✅ 信号量
支持 sem_timedwait()
简单的线程互斥
✅ 互斥锁
API 简单,性能最优

💡 核心原则

  • 线程间互斥:优先使用互斥锁(pthread_mutex_t
  • 进程间同步:优先使用有名信号量(sem_open
  • 资源计数:使用信号量
  • 实时系统/中断:使用信号量

6. 第六部分:高级并发工具

6.1 GLib 线程池

6.1.1 GLib 线程池简介

  • 头文件#include <glib.h>
  • 核心类型GThreadPool

设计目标

  • 避免频繁创建/销毁线程的开销;
  • 自动管理线程生命周期;
  • 支持任务队列、最大线程数限制、独占/共享模式;
  • 与 GLib 主循环、异步队列等无缝集成。

✅ 安装开发包(Ubuntu/Debian):

sudo apt install libglib2.0-dev

6.1.2 核心函数详解

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
exclusiveTRUE
:线程池独占线程(立即创建 max_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()
自定义任务排序规则

6.1.3 什么时候应该使用 GLib 线程池

适用场景对比表

场景
说明
是否推荐
✅ I/O 密集型任务
如文件读写、网络请求、数据库查询
✅ 大量短任务并发
避免 pthread_create 开销
✅ 与 GLib 生态集成
如 GTK 应用后台任务、GIO 异步操作
✅ 需要控制资源
限制最大并发线程数,防系统过载
❌ CPU 密集型并行计算
应考虑 OpenMP 或专用并行库

具体应用场景

  1. GTK/GNOME 应用

    • 后台数据处理
    • 文件 I/O 操作
    • 网络请求
  2. Linux 后台服务/守护进程

    • 处理客户端请求
    • 异步任务处理
    • 定时任务执行
  3. 多任务数据处理

    • 批量文件处理
    • 并行网络请求
    • 数据库批量操作
  4. 需要资源控制的场景

    • 限制并发连接数
    • 控制内存使用
    • 避免系统过载

不适用场景

  1. CPU 密集型计算(使用 OpenMP、MPI 等)
  2. 需要细粒度同步控制(直接用 pthread 更合适)
  3. 实时性要求极高的场景(使用实时线程)

6.1.4 完整示例 1:基本用法

// 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 个线程在复用,任务并发执行。


6.1.5 完整示例 2:使用共享数据

// 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,适合传递数据库连接、日志句柄等。


6.1.6 独占 vs 共享模式

模式对比表

模式
参数设置
行为
适用场景
独占(exclusive)exclusive=TRUE
线程池启动时立即创建 max_threads 个线程,不与其他池共享;适合高优先级、低延迟任务
实时任务、高频任务
共享(non-exclusive)exclusive=FALSE
线程按需创建,空闲线程可被 GLib 全局线程池回收,多个非独占池共享线程资源;节省内存
I/O 任务、低频任务、后台任务

详细说明

独占模式(exclusive=TRUE)

GThreadPool *pool = g_thread_pool_new(task_func, NULL3, TRUE, NULL);
  • 特点

    • 立即创建 3 个线程
    • 线程不会与其他池共享
    • 线程不会自动回收
  • 优点

    • 线程始终可用,无创建开销
    • 适合高优先级任务
    • 低延迟
  • 缺点

    • 占用更多内存
    • 闲置时浪费资源
  • 适用场景

    • 实时性要求高的任务
    • 高频任务处理
    • 响应时间敏感的应用

共享模式(exclusive=FALSE)

GThreadPool *pool = g_thread_pool_new(task_func, NULL-1, FALSE, NULL);
  • 特点

    • 线程按需创建
    • 空闲线程可被 GLib 全局池回收
    • 多个非独占池可共享线程
  • 优点

    • 节省内存
    • 自动管理线程生命周期
    • 适合 I/O 密集型任务
  • 缺点

    • 创建线程有开销
    • 可能出现短暂延迟
  • 适用场景

    • I/O 密集型任务
    • 低频任务
    • 后台处理
    • 内存受限环境

选择建议

💡 默认建议:

  • I/O 任务 → exclusive=FALSE
  • 实时任务 → exclusive=TRUE
  • 内存敏感 → exclusive=FALSE
  • 延迟敏感 → exclusive=TRUE

6.1.7 注意事项与最佳实践

注意事项

  1. 任务函数内避免长时间阻塞

    // ❌ 不好的做法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        }    }}
  2. 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);  // 释放内存}
  3. 不要在线程池任务中调用 g_thread_pool_free

    // ❌ 危险!可能导致死锁voiddangerous_task(gpointer data, gpointer user_data){    g_thread_pool_free(pool, FALSE, TRUE);  // 不要这样做!}
  4. 错误处理

    GError *error = NULL;GThreadPool *pool = g_thread_pool_new(task_func, NULL3, 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);}
  5. GLib 线程初始化

    // GLib ≥ 2.32 已自动初始化,无需手动调用// 但对于更早版本,需要:g_thread_init(NULL);

最佳实践

  1. 合理设置 max_threads

    // 根据 CPU 核心数设置int max_threads = g_get_num_processors();GThreadPool *pool = g_thread_pool_new(task_func, NULL, max_threads, FALSE, NULL);
  2. 使用独占模式处理实时任务

    // 实时任务池:独占模式,固定线程数GThreadPool *realtime_pool = g_thread_pool_new(    realtime_task, NULL2, TRUE, NULL);
  3. 使用共享模式处理 I/O 任务

    // I/O 任务池:共享模式,按需创建GThreadPool *io_pool = g_thread_pool_new(    io_task, NULL-1, FALSE, NULL);
  4. 正确清理资源

    // 等待所有任务完成再释放g_thread_pool_free(pool, FALSE, TRUE);// 不等待,立即丢弃未完成任务(谨慎使用)g_thread_pool_free(pool, TRUE, FALSE);
  5. 监控任务队列状态

    // 获取未处理任务数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);
  6. 处理任务优先级

    // 高优先级任务:移到队列头部g_thread_pool_push(pool, urgent_task, NULL);g_thread_pool_move_to_front(pool, urgent_task);
  7. 使用共享数据传递上下文

    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);}
  8. 处理大量任务时的内存管理

    // 批量提交任务时,注意内存释放for (int i = 0; i < 1000; i++) {    TaskData *data = create_task_data(i);if (!g_thread_pool_push(pool, data, NULL)) {free(data);  // 推送失败时释放    }}

性能优化建议

  1. 避免频繁创建/销毁线程池

    • 复用线程池对象
    • 不要为每个任务创建新池
  2. 合理使用共享数据

    • 避免数据竞争
    • 使用适当的同步机制
  3. 监控线程池状态

    • 定期检查队列长度
    • 动态调整 max_threads
  4. 处理错误和异常

    • 捕获任务函数中的异常
    • 记录错误日志
  5. 考虑线程池的局限性

    • 不适用于 CPU 密集型任务
    • 不适用于需要细粒度同步的场景

6.1.8 总结

特性
GLib 线程池
易用性
✅ 极高,几行代码即可并发
性能
✅ 基于 GLib 异步队列,高效
资源控制
✅ 支持最大线程数、任务队列
生态集成
✅ 与 GMainLoop、GAsyncQueue 无缝协作
适用语言
C / C++(通过 glibmm)

🎯 推荐使用场景: GTK/GNOME 应用、Linux 后台服务、需要轻量级并发的 C 项目。

通过 GLib 线程池,你可以轻松写出高性能、可维护的并发 C 程序,而无需手动管理线程生命周期和同步原语。


附录

参考资源

  • POSIX 线程标准:IEEE Std 1003.1
  • Linux man 手册
    • man pthread_create
    • man pthread_mutex_lock
    • man pthread_rwlock_rdlock
    • man sem_wait
    • man pthread_cond_wait
  • GLib 文档:https://docs.gtk.org/glib/

编译说明

编译 pthread 程序

gcc -o program program.c -pthread

编译信号量程序(无名)

gcc -o sem_program sem_program.c -pthread

编译信号量程序(有名)

gcc -o named_sem named_sem.c -lrt -pthread

编译 GLib 线程池程序

gcc -o glib_program glib_program.c `pkg-config --cflags --libs glib-2.0`

总行数:约 2000+ 行

章节数:17+ 个主要章节

代码示例:20+ 个完整示例

核心知识点

  • 进程与线程区别
  • POSIX 线程完整 API
  • 线程取消机制
  • 互斥锁、读写锁、自旋锁
  • 条件变量
  • 无名信号量与有名信号量
  • GLib 线程池

希望这份文档能帮助你深入理解 Linux 线程编程!

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-17 15:30:03 HTTP/2.0 GET : https://f.mffb.com.cn/a/485224.html
  2. 运行时间 : 0.237853s [ 吞吐率:4.20req/s ] 内存消耗:5,295.49kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=b64bca79489261d5186981e9d92b70e3
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000672s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000772s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.040606s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.004889s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000699s ]
  6. SELECT * FROM `set` [ RunTime:0.001026s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000610s ]
  8. SELECT * FROM `article` WHERE `id` = 485224 LIMIT 1 [ RunTime:0.003264s ]
  9. UPDATE `article` SET `lasttime` = 1776411003 WHERE `id` = 485224 [ RunTime:0.107905s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000360s ]
  11. SELECT * FROM `article` WHERE `id` < 485224 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000593s ]
  12. SELECT * FROM `article` WHERE `id` > 485224 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000378s ]
  13. SELECT * FROM `article` WHERE `id` < 485224 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001619s ]
  14. SELECT * FROM `article` WHERE `id` < 485224 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001118s ]
  15. SELECT * FROM `article` WHERE `id` < 485224 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002231s ]
0.239538s