%% # 纲要 > 主干纲要、Hint/线索/路标 - **线程的创建与管理**:创建、启动和管理线程 - **线程间同步**:线程之间如何协作,保护共享数据,避免条件竞争; - 锁 - 互斥量 `pthread_mutex_t` - 读写锁 `pthread_rwlock_t` - 自旋锁 `pthread_spinlock_t` - 条件变量 `pthread_cond_t` - 信号量 `sem_t` - 屏障 `pthread_barrier_t` - **线程间通信** - Futures and Promises:用于从异步操作中接收结果。 - 任务队列和工作窃取:何使用任务队列以及如何实现高效的工作分配和负载平衡 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # POSIX 多线程&同步头文件总结 同步原语(互斥锁、条件变量、信号量)的**原理说明**参见[[02-开发笔记/05-操作系统/并发相关/同步原语|同步原语]] ⭐,**下文直接简要介绍 API**。 POSIX 线程与同步相关的头文件汇总: | 头文件 | 功能 | | --------------- | ---------------------------------------------------------------------------------------------------------- | | `<pthread.h>` | **POSIX 线程库**,提供**线程管理**、**互斥量**、**条件变量**相关 API (`pthread_create`, `pthread_mutex_t`, `pthread_cond_t` 等) | | `<semaphore.h>` | **POSIX 信号量**,用于线程间同步(`sem_init`, `sem_wait`, `sem_post` 等) | | `<unist.h>` | 提供**线程休眠**、系统调用等基础功能。 | > [!quote] C++ 线程 & 同步相关的 API 参见 [[02-开发笔记/01-cpp/多线程并发相关/cpp-多线程&同步|cpp-多线程&同步]] > <br><br> ### POSIX 线程库与 C++ 线程库比较 | 库 | **POSIX 线程库** | **C++ 标准线程库** | Boost 线程库 | | ----------- | ------------------------------------------------- | --------------------------------------- | --------- | | 头文件 | `<pthread.h>` | `<thread>` | | | 说明 | **POSIX 标准定义的线程库**,主要用于 C 语言,也支持 C++ | C++标准库的一部分 (since C++11) | | | **语法** | 底层 API,面向过程的**函数调用**风格。 | **面向对象**风格,结合**RAII**范式 | | | **线程管理** | **手动管理**线程生命周期,使用`pthread_create`,`pthread_join`等 | 通过 **“线程对象” 自动管理线程的生命周期** | | | **同步原语** | `pthread_mutex_t`,`pthread_cond_t`等 | `std::mutex`,`std::condition_variable`等 | | | **错误处理** | 通过**返回值手动处理错误** | **基于 C++异常机制** | | | **与 C++集成** | 无法直接与 C++特性(如 RAII、异常)集成 | 完全符合 C++特性,如 lambda、RAII 等 | | | **跨平台支持** | **UNIX-like 系统** | **跨平台支持**,包括 Windows、Linux、macOS | | > [!example] 创建线程 > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-6EF1A38AB19B7D385C7790BCDC4F1509.png|915]] <br><br> ### POSIX 线程同步 API 中的资源生命周期管理 POSIX API,对互斥量、条件变量、信号量、屏障等同步原语的资源管理,需要**手动初始化 & 释放资源**: - (1)**资源分配 & 初始化**(部分 API 额外支持静态初始化) - 静态初始化:适用于 **静态变量**,可利用 "**宏**" 值直接进行初始化; - 动态初始化:由 `*_init()` 函数进行; - (2)**资源释放**: - 由 `*_destroy()` 函数完成 | 变量类型 | 静态初始化宏 | 动态初始化 | 资源释放 | | ------------------- | ---------------------------- | -------------------------------------- | ------------------------------- | | `pthread_mutex_t` | `PTHREAD_MUTEX_INITIALIZER` | `pthread_mutex_init(&mtx, nullptr)` | `pthread_mutex_destroy(&mtx)` | | `pthread_cond_t` | `PTHREAD_COND_INITIALIZER` | `pthread_cond_init(&cv, nullptr)` | `pthread_cond_destroy(&cv)` | | `pthread_rwlock_t` | `PTHREAD_RWLOCK_INITIALIZER` | `pthread_rwlock_init(&lock, nullptr)` | `pthread_rwlock_destroy(&lock)` | | `pthread_barrier_t` | ❌ 不支持 | `pthread_barrier_init(&b, nullptr, n)` | `pthread_barrier_destroy(&b)` | | `sem_t` | ❌ | `sem_init(&sem, 0, n)` | `sem_destroy(&sem)` | 此外,**属性相关的变量** 也均需要 "**手动初始化 & 释放资源**" | 变量类型 | 初始化 | 销毁 | | ----------------------- | --------------------------------- | ------------------------------------ | | `pthread_attr_t` | `pthread_attr_init(&attr)` | `pthread_attr_destroy(&attr)` | | `pthread_mutexattr_t` | `pthread_mutexattr_init(&attr)` | `pthread_mutexattr_destroy(&attr)` | | `pthread_rwlockattr_t` | `pthread_rwlockattr_init(&attr)` | `pthread_rwlock_attr_destroy(&attr)` | | `pthread_condattr_t` | `pthread_condattr_init(&attr)` | `pthread_condattr_destroy(&attr)` | | `pthread_barrierattr_t` | `pthread_barrierattr_init(&attr)` | `pthread_barrierattr_destroy(&attr)` | #### 说明示例 示例一:静态初始化 + 资源释放 ```cpp // 静态初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int main() { pthread_mutex_lock(&mutex); // ... pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); // 释放 return 0; } ``` 示例二:动态初始化 + 资源释放 ```cpp pthread_mutex_t mutex; // 未使用宏进行静态初始化 int main() { pthread_mutex_init(&mutex, nullptr); // 动态初始化 phtread_mutex_lock(&mutex); // ... pthread_mutexunlock(&mutex); pthread_mutex_destroy(&mutex); // 释放 } ``` <br><br><br> # POSIX 线程 > 头文件 `<pthread.h>`,API 说明参见[^1] > [!NOTE] POSIX 进程 API 与线程 API 的比较 > > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-611C25C4054BB1D38BEE39D223E063C4.png|633]] > ^1o2a91 <br> ## 线程创建、终止、清理 #### pthread_create 线程创建 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-D2CA1E359C688D8170549A909F5E6752.png|713]] > [!important] POSIX API 下,对**线程函数的定义形式**有明确要求:形如 `void* thread_func(void* arg);` > > - 返回值类型:`void*` > - 可**接收任意类型参数**:在 `pthread_create()` 创建线程时传入,在线程函数内获取该参数(通过强制类型转换) > - 参数类型:`void*`(唯一) > - 可**返回任意类型结果**:线程函数内通过 `pthread_exit()` 或 `return` 返回结果,外层线程通过 `pthread_join()` 获取返回值。 > > #### pthread_exit 线程退出 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-B191904FF1D68746E89EE896E11BFD95.png|784]] > [!info] **单个线程**可通过三种方式退出: > > 1. 正常 `return` 返回; > 2. 主动**调用 `pthread_exit()`** 退出; > 3. 被其他线程 **调用 `pthread_cancel()`** 取消该线程 #### pthread_join 资源回收 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-B3ED39612B4402F180E1DD97324B62DD.png|719]] 线程退出后,系统不会立即释放其占用资源(独立栈空间、线程控制块)等,需由**调用线程通过 `pthread_join()` ==阻塞等待目标线程结束==并回收资源**。 示例: ```c #include <pthread.h> #include <stdio.h> #include <assert.h> // 线程函数, 以`void*`接收任意类型参数, 以 `void*`返回任意类型参数. void* threadFunc(void* arg) { pthread_t tid = pthread_self(); // 获取自身线程id; printf("Thread[%lu]: %s\n", (unsigned long) tid, (const char*) arg); return NULL; } int main() { printf("Main:begin\n"); pthread_t p1, p2; int rc; // 创建两个线程 rc = pthread_create(&p1, NULL, threadFunc, "A"); assert(rc == 0); rc = pthread_create(&p2, NULL, threadFunc, "B"); assert(rc == 0); // join阻塞等待线程完成 rc = pthread_join(p1, NULL); assert(rc == 0); rc = pthread_join(p2, NULL); assert(rc == 0); printf("Main:end\n"); return 0; } ``` <br> ## 线程分离 可通过 "**线程属性**" 设置线程为 "**分离状态**",此后无需由其调用线程通过 `pthread_join()` 等待并回收资源,而系统会在 **==线程终止时==自动回收资源**。 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-FB2101A26F71D5C0C4F71CF6C0D64922.png|812]] ```c #include <pthread.h> #include <stdio.h> void* threadFunc(void* arg) { pthread_t tid = pthread_self(); // 获取自身线程id; printf("Thread: %lu\n", (unsigned long) tid); return NULL; } int main() { pthread_t t1; pthread_create(&t1, NULL, threadFunc, NULL); // 分离线程, 该线程终止时由系统自动回收其资源 pthread_detach(t1); // 无需主线程再调用pthread_join(). } ``` <br> ## 线程函数参数传递、返回值传递 使用示例: ```cpp #include <pthread.h> #include <unistd.h> // getpid #include <cstdio> #include <cassert> using namespace std; void* threadFunc(void* arg) { pthread_t tid = pthread_self(); int* num = static_cast<int*>(arg); printf("Thread[%ld]: %d\n", static_cast<unsigned long>(tid), *num); int* result = new int(*num + 100); return result; // C++支持任意类型指针转void*. } int main() { pthread_t t1, t2; int num1 = 1, num2 = 2; // 通过create传递参数 int err; err = pthread_create(&t1, nullptr, threadFunc, &num1); assert(err == 0); err = pthread_create(&t2, nullptr, threadFunc, &num2); assert(err == 0); // 通过join获取返回值 void *res1, *res2; pthread_join(t1, &res1); // 第二参数是`void**`类型 pthread_join(t2, &res2); int *val1 = static_cast<int*>(res1), *val2 = static_cast<int*>(res2); printf("%d, %d\n", *val1, *val2); delete val1; delete val2; } ``` > [!note] C++ thead API 中,支持**向线程函数传递参数(多个**),但不支持直接返回结果。 > > 若需要执行异步任务并返回结果,需搭配使用 `std::async()` 与 `std::future<>` 。 <br> <br><br><br> # POSIX 锁 ## POSIX 互斥锁 > 头文件 `<pthread.h>` ,API 说明参见 [^2] ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-F06478BDF354BF5DD07FED66ABC397F7.png|662]] 使用流程示例: ```C // 静态初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 动态初始化 pthread_mutex_t mutex; pthread_mutex_init(&mutex, nullptr); // 加锁 pthread_mutex_lock(&mutex); // 阻塞 pthread_mutex_trylock(&mutex); // 非阻塞, 无法获取锁时返回EBUSY // 释放锁 pthread_mutex_unlock(&mutex); // 销毁锁 pthread_mutex_destroy(&mutex); ``` <br> ## POSIX 读写锁 > 头文件 `<pthread.h>` ,API 说明参见[^3] > [!NOTE] "读写锁" 的概念说明参见:[[02-开发笔记/05-操作系统/并发相关/同步原语#读写锁|同步原语#读写锁]] > > POSIX 中提供的实现是 "**读优先锁**",即当**读锁**已被持有时,仍可继续请求读锁,而**写锁将一直阻塞**直至所有读锁释放。 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-B8C93B8A1E57C02C930160E059CF0BEE.png|749]] <br> ## POSIX 自旋锁 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-6B145BA6FA79ABB53CFE8873D384DB84.png|731]] ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-3D0BA182A915149B0FB58B746271C645.png|678]] <br><br><br> # POSIX 条件变量 > 头文件 `<pthread.h>` ,API 说明参见[^4] > [!NOTE] "条件变量" 的概念说明参见:[[02-开发笔记/05-操作系统/并发相关/同步原语#条件变量|同步原语#条件变量]] ### 初始化条件变量 - (1)静态初始化:`pthread_cond_t cond = PTHREAD_COND_INITIALIZER;` - (2)动态初始化:针对 "**动态分配的条件变量**",需使用 `pthread_cond_init()` 函数动态初始化条件变量, - 示例:`pthread_cond_t cond = pthread_cond_init(&cond, NULL)` 使用流程示例: ```cpp // 1) --------静态初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 2) --------动态初始化 pthread_cond_t cond; pthread_contattr_t attr; // 初始化条件变量属性 pthread_condattr_init(&attr); // 初始化条件变量 pthread_cond_init(&cond, &attr); // 默认属性时, 第二项参数可直接传nullptr. // 销毁条件变量属性对象 pthread_condattr_destory(&attr); // 销毁条件变量 pthread_cond_destory(&cond); ``` ### 条件变量操作 API ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-C77BE95683AC2EC147AD631BCB982D77.png|675]] > [!NOTE] 调用上述函数时必须先持有 "互斥锁",因为条件状态本身属于 "共享资源",改变条件状态需受保护 > [!NOTE] `pthread_cond_wait()` 会对加锁的互斥量解锁 > > 该函数将 **加锁后的互斥量** 作为其第二个参数,其会让调用线程进入"**休眠**"同时 **==释放锁==**(否则**其他线程无法获取锁并将其唤醒**)。当休眠线程被其他线程唤醒而**从 `pthread_cond_wait()` 返回时**,其将**再次获得互斥锁**。 > > 注:`pthread_cond_wait()` 直到 "**==被唤醒==**" & "**==获得指定锁==**" 后才会返回。 ![[02-开发笔记/05-操作系统/并发相关/同步原语#^6hqcq5]] <br> ### 使用示例 ```cpp #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <assert.h> int done = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 通知线程 void* thr_exit(void* arg) { int rc; // 修改共享变量done, 条件变量cond都需要加锁. rc = pthread_mutex_lock(&mutex); assert(rc == 0); done = 1; rc = pthread_cond_signal(&cond); assert(rc == 0); rc = pthread_mutex_unlock(&mutex); assert(rc == 0); printf("thr_exit() has been called.\n"); return NULL; } // 等待线程 void* thr_join(void* arg) { int rc; // 检查共享变量done, 需要加锁. rc = pthread_mutex_lock(&mutex); assert(rc == 0); while (done == 0) { // 当done为0时, 线程进入休眠等待状态, 释放互斥锁, 等待被唤醒. rc = pthread_cond_wait(&cond, &mutex); assert(rc == 0); } rc = pthread_mutex_unlock(&mutex); assert(rc == 0); printf("thr_join() has been called.\n"); return NULL; } /* */ int main() { pthread_t p1, p2; int rc; rc = pthread_create(&p1, NULL, thr_exit, NULL); assert(rc == 0); rc = pthread_create(&p2, NULL, thr_join, NULL); assert(rc == 0); pthread_join(p1, NULL); pthread_join(p2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } ``` <br><br><br> # POSIX 信号量 > 头文件 `<semaphore.h> ` > [!NOTE] "信号量" 的概念说明参见:[[02-开发笔记/05-操作系统/并发相关/同步原语#信号量|同步原语#信号量]] POSIX 信号量有两种类型[^5] [^6]: - **具名**信号量(文件形式) - **匿名**信号量(内存变量) 二者均可用于 "**进程间同步**" & "**线程间同步**"。 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-F57BCEE79354B91705F01F0B4A484785.png|352]] | | 匿名信号量 | 具名信号量 | | ----- | --------------------------------------------- | ------------------------------ | | 形式 | `sem_t` 变量 | 文件系统路径名 | | 创建方式 | `sem_init()` | `sem_open()` | | 销毁方式 | `sem_destroy()` | `sem_close()` + `sem_unlink()` | | 进程间共享 | ✔️(需**设置 shared 参数非 0**,并将 `sem_t` 变量至于共享内存区) | ✔️(天然跨进程共享) | | 持久性 | 存在于内存,进程退出后销毁 | 存在于系统,直至 `sem_unlink` 删除 | ## 信号量操作 ###### sem_wait & sem_trywait ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-0DB9771DD1807752591B6D36B894A867.png|616]] <br> ###### sem_post & sem_getvalue ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-4C0BD8C6F8518AFE36DB19BA596A7C82.png|628]] <br> ## 匿名信号量 ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-8095DB25F1E5727A5B7A621AB1797B11.png|656]] 匿名信号量主要用于 "**线程间同步**",此时 `sem_init()` 第二参数需为 `0`,表示**不在进程间共享**。 > [!NOTE] 匿名信号量可用于 "线程间同步" & "进程间同步" [^7] > > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-1D91616F7E6873ED9038F5F221505859.png|432]] > 使用流程示例: ```c // 声明信号量 sem_t sem; // 初始化信号量为指定值 sem_init(&sem, 0, value) // P: 等待值为正, 并-1; sem_wait(&sem); // V: 令值+1, 并唤醒一个等待线程或进程 sem_post(&sem); // 销毁 sem_destroy(&sem); ``` <br> ## 具名信号量 > 参见 [^7] [^6]: > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-F61EF835F63FB67187428ECF10AF030A.png|608]] 具名信号量适用于 "**进程间同步**": - `sem_open()`:创建 or 打开一个已存在的**具名信号量**,返回指向该信号量的 `sem_t*` 指针; - `sem_close()`:关闭当前进程/线程打开的**具名信号量**(仅关闭,而非删除,具名信号量随内核持续) - `sem_unlink()`: 从内核中**移除指定的具名信号量**(不影响已被打开/引用的信号量,销毁会延迟至最后一个引用结束) 使用流程示例: ```c int main() { // 创建具名信号量 sem_t* sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0644, 0); if (sem == SEM_FAILED) { perror("sem_open"); return 1; } // ... sem_post(sem); // ... sem_wait(sem); // ... sem_close(sem); // 关闭引用 sem_unlink(SEM_NAME); // 清除具名信号量(使用路径名) } ``` <br><br><br> # POSIX 屏障 > 头文件 `<pthread.h>` ,API 说明参见[^8] 屏障(Barrier)机制允许 **==一组线程==在执行到某个 "同步点" 时均==陷入等待==**,**直至所有合作线程均到达该同步点后,各线程才会继续执行**。 > [!info] 屏障会自动重置并被复用 > 当**指定数量线程**到达同步点,全部通过屏障之后,**屏障会被==自动重置为初始值==**,支持被再次复用。 <br> ## 屏障的用法 1. **声明一个屏障变量**:**`pthread_barrier_t` 类型** 2. **初始化屏障**:指定 "**屏障计数**"——**屏障所需等待的线程数**。 - 调用 `pthread_barrier_init(&barrier, NULL, count)`; 3. **使用屏障**:任一线程**调用 `pthread_barrier_wait()` 而陷入阻塞**,直至**指定的屏障计数满足后**,**所有阻塞线程均被唤醒**。 > [!info] 屏障 API > > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-820D08243FC40DE9D4497E220548ECDD.png|694]] > > ![[_attachment/02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API.assets/IMG-POSIX 线程&同步 API-25D384D6D02D113C4B97A8EEDB952E1A.png|676]] > > #### 使用示例 ```cpp #include <pthread.h> #include <semaphore.h> // 多个H线程和O线程, 必须同时阻塞直至凑齐一个H2O后三个线程才能分别执行打印. // 1) 屏障用于等待凑齐3个线程 => 计数值为3的屏障 // 2) 最多只能允许2个H线程接触屏障, 需阻塞多余H线程. => 值为2的信号量 // 3) 最多只能允许1个O线程接触屏障, 需阻塞多余O线程. => 值为1的信号量 class H2O { public: H2O() { pthread_barrier_init(&barrier, nullptr, 3); sem_init(&sem_h_in, 0, 2); // 只允许2个H接触屏障 sem_init(&sem_o_in, 0, 1); // 只允许1个O接触屏障 } ~H2O() { pthread_barrier_destroy(&barrier); sem_destroy(&sem_h_in); sem_destroy(&sem_o_in); } void hydrogen(function<void()> releaseHydrogen) { sem_wait(&sem_h_in); pthread_barrier_wait(&barrier); releaseHydrogen(); sem_post(&sem_h_in); // 唤醒下一个阻塞的h } void oxygen(function<void()> releaseOxygen) { sem_wait(&sem_o_in); pthread_barrier_wait(&barrier); releaseOxygen(); sem_post(&sem_o_in); // 唤醒下一个阻塞的o } private: pthread_barrier_t barrier; sem_t sem_h_in; sem_t sem_o_in; }; ``` <br><br><br> # POSIX `pthread_once` `pthread_once(*init_flag, func)` 函数搭配 `pthread_once_t` 类型的变量 `init_flag`使用:当多个线程均调用了 `pthread_once()` 时,**系统保证仅首次调用有效**,**其余调用会在首次初始化时阻塞**,**初始化完成后的所有再次调用都会直接跳过**。 ```c // 声明once_flag pthread_once_t init_flag = PTHREAD_ONCE_INIT; void resource_init(); // 某个共享资源的初始化函数 void* thread_task(void* arg) { pthread_once(&init_flag, resource_init); //在所有线程中, 仅首次调用有效. // ... } ``` <br><br> # 参考资料 - 《Unix 环境高级编程》 - 第 11 章:线程 - 11.6 线程同步 - 11.6.1 互斥量 - 11.6.4 读写锁 - 11.6.6 条件变量 - 11.6.8 屏障 - 第 12 章:线程控制 - 《操作系统导论》OSTEP - 第二部分:并发 - 《Unix 环境网络编程卷 2:进程间通信》 - 第 7 章:互斥锁和条件变量 - 第 10 章:POSIX 信号量 # Footnotes [^1]: 《Unix 环境高级编程》P309 [^2]: 《Unix 环境高级编程》P320 [^3]: 《Unix 环境高级编程》P328 [^4]: 《Unix 环境高级编程》P332 [^5]: 《Unix 环境网络编程卷 2:进程间通信》P175 [^6]: 《Unix 环境高级编程》P465~469 [^7]: 《Unix 环境网络编程卷 2:进程间通信》P179 [^8]: 《Unix 环境高级编程》P336