实战:将读者 - 写者模型封装为静态库与动态库及差异分析
·
在多线程编程中,读者 - 写者问题是经典的同步互斥场景。本文将演示如何将读者 - 写者模型封装为可复用的静态链接库(.a)和动态链接库(.so),并通过工具命令分析两种库的差异,帮助理解链接机制的底层逻辑。
一、环境与代码准备
1. 环境
- 系统:Linux(本文基于 openEuler)
- 工具:
gcc(编译器)、ar(静态库打包)、readelf(ELF 分析)、ldd(动态依赖查看)、size(段大小分析)
2. 代码拆分
为实现库封装,将代码拆分为 3 个文件(分离接口与实现):
-
rw_lib.h:库接口声明(结构体、函数原型)#ifndef RW_LIB_H #define RW_LIB_H #include <pthread.h> #include <semaphore.h> #define MAX_THREADS 5 #define MAX_OPERATIONS 3 // 共享资源控制块 typedef struct { sem_t rw_mutex; // 读写互斥锁 sem_t mutex; // 读者计数保护锁 int read_count; // 当前读者数量 } RWControl; // 线程参数 typedef struct { int id; RWControl* rw_ctrl; } ThreadArgs; // 接口函数 void rw_init(RWControl* rw_ctrl); void rw_destroy(RWControl* rw_ctrl); void* reader_thread(void* arg); void* writer_thread(void* arg); #endif -
rw_lib.c:库功能实现(读者 / 写者逻辑)#include "rw_lib.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> void rw_init(RWControl* rw_ctrl) { sem_init(&rw_ctrl->rw_mutex, 0, 1); sem_init(&rw_ctrl->mutex, 0, 1); rw_ctrl->read_count = 0; } void rw_destroy(RWControl* rw_ctrl) { sem_destroy(&rw_ctrl->rw_mutex); sem_destroy(&rw_ctrl->mutex); } // 读者线程实现(略,见完整逻辑) void* reader_thread(void* arg) { /* ... */ } // 写者线程实现(略,见完整逻辑) void* writer_thread(void* arg) { /* ... */ } -
main.c:测试程序(调用库接口)#include "rw_lib.h" #include <stdio.h> #include <pthread.h> #include <time.h> int main() { srand(time(NULL)); RWControl rw_ctrl; rw_init(&rw_ctrl); pthread_t readers[MAX_THREADS], writers[MAX_THREADS]; ThreadArgs r_args[MAX_THREADS], w_args[MAX_THREADS]; // 创建读者/写者线程(略) // 等待线程结束并销毁资源(略) return 0; }
二、生成静态链接库(.a)及分析
静态库是目标文件的归档包,编译时会被完整嵌入可执行文件,运行时不依赖外部文件。
1. 生成静态库步骤
# 1. 编译库实现为目标文件
gcc -c rw_lib.c -o rw_lib.o
# 2. 用ar打包为静态库(命名规则:libxxx.a)
ar rcs librw.a rw_lib.o
# 3. 链接静态库生成可执行程序
gcc main.c -o rw_static -L. -lrw -lpthread
-L.:指定当前目录为库搜索路径;-lrw:链接librw.a;-lpthread:链接系统线程库。
2. 运行与验证
# 运行静态链接程序
./rw_static
输出示例:
[读者] 1号开始读取
[写者] 1号开始书写
...(读写过程)
所有线程结束
3. 用size查看段大小
size rw_static
输出示例:
text data bss dec hex filename
16466 640 288 17394 4432 rw_static
text(代码段):包含库和主程序的所有指令(最大,因静态库代码已嵌入)。data/bss:数据段(初始化 / 未初始化的全局变量)。
三、生成动态链接库(.so)及分析
动态库(共享库)编译时仅记录引用,运行时才加载,可被多个程序共享,节省内存。
1. 生成动态库
# 1. 编译位置无关目标文件(-fPIC确保地址无关)
gcc -c -fPIC rw_lib.c -o rw_lib_so.o
# 2. 生成动态库(命名规则:libxxx.so)
gcc -shared rw_lib_so.o -o librw.so
# 3. 链接动态库生成可执行程序
gcc main.c -o rw_dynamic -L. -lrw -lpthread
2. 运行与验证
动态库需指定搜索路径(临时设置环境变量):
# 设置动态库路径
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
# 运行动态链接程序
./rw_dynamic
输出与静态程序一致(逻辑相同,仅链接方式不同)。
3. 用ldd查看动态依赖
ldd rw_dynamic
输出示例:
linux-vdso.so.1
librw.so => ./librw.so # 依赖自定义动态库
libpthread.so.0 => /lib64/libpthread.so.0 # 系统线程库
libc.so.6 => /lib64/libc.so.6 # 系统C库
明确显示程序依赖librw.so及系统库。
四、静态库与动态库的 ELF 格式差异(readelf分析)
ELF(Executable and Linkable Format)是 Linux 可执行文件 / 库的标准格式,通过readelf可观察两种链接方式的本质区别。
1. 静态链接程序(rw_static)
# 查看动态段(无动态依赖)
readelf -d rw_static
# 输出:There is no dynamic section in this file.
# 查看符号表(库函数已嵌入)
readelf -s rw_static | grep "reader_thread"
# 输出:123: 00000000004011a6 99 FUNC GLOBAL DEFAULT 14 reader_thread
- 无动态段,库函数已嵌入代码段(
DEFAULT)。
2. 动态链接程序(rw_dynamic)
# 查看动态段(含依赖信息)
readelf -d rw_dynamic
# 输出关键项:
# 0x00000001 (NEEDED) Shared library: [librw.so]
# 0x0000000f (RPATH) Library rpath: [.]
# 查看符号表(库函数为外部引用)
readelf -s rw_dynamic | grep "reader_thread"
# 输出:102: 0000000000000000 0 FUNC GLOBAL DEFAULT UND reader_thread
- 有动态段,明确依赖
librw.so;库函数标记为UND(未定义),运行时动态加载。
五、总结
| 特性 | 静态库(.a) |
动态库(.so) |
|---|---|---|
| 链接方式 | 代码嵌入可执行文件 | 仅记录引用,运行时加载 |
| 可执行文件体积 | 较大(包含库代码) | 较小(仅含主程序和引用) |
| 运行依赖 | 无(独立运行) | 依赖动态库文件存在 |
动态段(readelf) |
无 | 有,含NEEDED依赖信息 |
| 符号表(函数) | 已嵌入(DEFAULT) |
外部引用(UND) |
| 适用场景 | 小库、需独立分发的程序 | 大库、多程序共享的场景 |
通过本文实践,可深入理解静态链接与动态链接的底层机制,为多线程程序的模块化设计和库管理提供参考。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)