多线程并发编程笔记:线程竞争资源模型
一、代码示例对比分析
示例1:带线程ID的窗口竞争模型
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <time.h> int win = 3; // 可用窗口数 pthread_mutex_t mutex; void *th(void *arg) { int id = *(int *)arg; // 获取线程ID sleep(rand() % 5); // 随机延迟0-4秒 while (1) // 持续尝试直到成功 { pthread_mutex_lock(&mutex); if(win > 0) // 检查是否有可用窗口 { win--; // 占用一个窗口 pthread_mutex_unlock(&mutex); printf("%d:get win\n", id); // 显示获取成功 sleep(rand() % 3 + 1); // 模拟使用窗口1-3秒 pthread_mutex_lock(&mutex); win++; // 释放窗口 pthread_mutex_unlock(&mutex); printf("%d:relese win\n", id); // 显示释放 break; // 退出循环 } else { printf("%d: 没有可用窗口,等待...\n", id); pthread_mutex_unlock(&mutex); sleep(1); // 等待1秒后重试 } } return NULL; } int main(int argc, char **argv) { srand(time(NULL)); // 初始化随机种子 pthread_t tid[10]; // 线程ID数组 int id[10]; // 线程编号数组 // 初始化线程编号 for (int i = 0; i < 10; i++) { id[i] = i + 1; } pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 // 创建10个线程 for (int i = 0; i < 10; i++) { pthread_create(&tid[i], NULL, th, &id[i]); } // 等待所有线程结束 for (int i = 0; i < 10; i++) { pthread_join(tid[i], NULL); } pthread_mutex_destroy(&mutex); // 销毁互斥锁 return 0; }示例2:匿名线程竞争模型
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> int win = 3; // 可用窗口数 pthread_mutex_t mutex; void* th(void* arg) { while (1) { pthread_mutex_lock(&mutex); if (win > 0) // 检查是否有可用窗口 { win--; // 占用窗口 pthread_mutex_unlock(&mutex); printf("get win...\n"); // 获取成功 sleep(rand() % 5 + 1); // 使用窗口1-5秒 pthread_mutex_lock(&mutex); win++; // 释放窗口 printf("relese win...\n"); pthread_mutex_unlock(&mutex); break; // 完成任务 } else { pthread_mutex_unlock(&mutex); // 这里没有等待逻辑,会快速重试 } } return NULL; } int main(int argc, char** argv) { int i = 0; srand(time(NULL)); // 初始化随机种子 pthread_t tid[10] = {0}; // 线程ID数组 pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 // 创建10个线程 for (i = 0; i < 10; i++) { pthread_create(&tid[i], NULL, th, NULL); // 不传递参数 } // 等待所有线程结束 for (i = 0; i < 10; i++) { pthread_join(tid[i], NULL); } pthread_mutex_destroy(&mutex); // 销毁互斥锁 return 0; }示例3:多互斥锁竞争模型(trylock实现)
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> pthread_mutex_t mutex1; // 窗口1的锁 pthread_mutex_t mutex2; // 窗口2的锁 pthread_mutex_t mutex3; // 窗口3的锁 void* th(void* arg) { int ret = 0; while (1) { // 尝试获取窗口1 ret = pthread_mutex_trylock(&mutex1); if (0 == ret) { printf("get win1...\n"); sleep(rand() % 5 + 1); printf("release win1...\n"); pthread_mutex_unlock(&mutex1); break; } else { // 尝试获取窗口2 ret = pthread_mutex_trylock(&mutex2); if (0 == ret) { printf("get win2...\n"); sleep(rand() % 5 + 1); printf("release win2...\n"); pthread_mutex_unlock(&mutex2); break; } else { // 尝试获取窗口3 ret = pthread_mutex_trylock(&mutex3); if (0 == ret) { printf("get win3...\n"); sleep(rand() % 5 + 1); printf("release win3...\n"); pthread_mutex_unlock(&mutex3); break; } else { // 所有窗口都忙,可以添加等待逻辑 // 当前版本会忙等待 } } } } return NULL; } int main(int argc, char** argv) { int i = 0; srand(time(NULL)); pthread_t tid[10] = {0}; pthread_mutex_init(&mutex1, NULL); pthread_mutex_init(&mutex2, NULL); pthread_mutex_init(&mutex3, NULL); for (i = 0; i < 10; i++) { pthread_create(&tid[i], NULL, th, NULL); } for (i = 0; i < 10; i++) { pthread_join(tid[i], NULL); } pthread_mutex_destroy(&mutex1); pthread_mutex_destroy(&mutex2); pthread_mutex_destroy(&mutex3); return 0; }二、三种模型对比分析
1.设计思路对比
模型 | 核心思想 | 资源表示 | 竞争方式 |
|---|---|---|---|
模型1 | 集中式管理 | 计数器( | 所有线程竞争一个锁,检查计数器 |
模型2 | 简化集中式 | 计数器(win) | 同上,但无线程ID和等待消息 |
模型3 | 分布式管理 | 三个独立锁 | 每个窗口独立锁,trylock轮询 |
2.实现复杂度对比
模型 | 锁数量 | 代码复杂度 | 灵活性 |
|---|---|---|---|
模型1 | 1个互斥锁 | 中等 | 窗口数量易扩展 |
模型2 | 1个互斥锁 | 简单 | 功能最简 |
模型3 | 3个互斥锁 | 较高 | 每个窗口独立控制 |
3.性能特点对比
| 模型 | 锁竞争 | 公平性 | 并发度 |
|---|---|---|---|
| 模型1 | 高(一个锁) | FIFO近似公平 | 低 |
| 模型2 | 高(一个锁) | 不确定 | 低 |
| 模型3 | 低(三个锁) | 优先抢占 | 高 |
三、关键技术点详解
1.pthread_mutex_trylock 使用
int ret = pthread_mutex_trylock(&mutex); // 返回值: // 0: 成功获取锁 // EBUSY: 锁已被占用 // 其他: 错误
2.锁的初始化与销毁
// 初始化 pthread_mutex_init(&mutex, NULL); // 或静态初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 销毁 pthread_mutex_destroy(&mutex);
3.线程创建与等待
// 创建线程 pthread_create(&tid[i], NULL, thread_func, &arg); // 等待线程结束 pthread_join(tid[i], NULL);
四、问题与改进
1.问题
示例1.2问题
Q1: 为什么线程执行顺序不确定?
操作系统线程调度具有随机性
sleep(rand() % 5)引入随机延迟锁竞争结果不确定
Q2: 如何保证线程安全?
使用互斥锁保护所有共享资源访问
确保加锁和解锁配对出现
避免在临界区内进行耗时操作
Q3: 为什么需要pthread_join?
等待线程完成,防止主线程提前退出
回收线程资源
确保所有任务完成
Q4: 如何优化性能?
减少临界区内的操作
使用读写锁(如果适用)
考虑无锁数据结构
示例3的问题
忙等待问题:所有窗口都忙时,线程会快速轮询,消耗CPU
饥饿问题:可能某些线程永远抢不到窗口
优先级固定:总是先尝试窗口1,然后是2、3
2.改进方案:
// 改进版:添加随机等待和公平性 void* th_improved(void* arg) { int ret = 0; int try_count = 0; while (1) { // 随机选择尝试顺序,提高公平性 int order[3] = {0, 1, 2}; shuffle(order, 3); // 随机打乱顺序 for (int i = 0; i < 3; i++) { pthread_mutex_t* mutexes[3] = {&mutex1, &mutex2, &mutex3}; ret = pthread_mutex_trylock(mutexes[order[i]]); if (0 == ret) { printf("get win%d...\n", order[i]+1); sleep(rand() % 5 + 1); printf("release win%d...\n", order[i]+1); pthread_mutex_unlock(mutexes[order[i]]); return NULL; } } // 所有窗口都忙,指数退避等待 try_count++; int wait_time = (1 << try_count); // 指数增长 if (wait_time > 8) wait_time = 8; printf("all windows busy, waiting %d seconds...\n", wait_time); sleep(wait_time); } return NULL; }五、应用场景建议
1.使用模型1(计数器)的场景:
资源完全同质化
需要精确控制资源总数
扩展性要求高(窗口数量可能变化)
2.使用模型2(简化版)的场景:
快速原型开发
调试和演示
资源竞争不激烈
3.使用模型3(多锁)的场景:
资源有差异(如不同服务窗口)
需要高并发性能
可以接受一定的不公平性
六、最佳实践总结
根据需求选模型:
同质资源 → 计数器模型
异质资源 → 多锁模型
简单场景 → 简化模型
避免忙等待:
trylock失败后适当sleep
使用指数退避策略
考虑使用条件变量
确保资源释放:
每个lock都要有对应的unlock
所有退出路径都要释放锁
主线程等待所有子线程
错误处理:
检查pthread函数返回值
添加适当的日志输出
考虑线程安全的错误处理
性能优化:
减少临界区大小
避免在锁内进行IO操作
根据竞争程度调整策略
七、扩展思考
信号量替代方案:可以使用信号量(semaphore)更简洁地实现资源计数
条件变量:使用条件变量避免忙等待,节省CPU资源
线程池:对于频繁创建销毁的场景,考虑使用线程池
这个模型是多线程编程的基础模式,广泛应用于:
数据库连接池
网络服务器连接管理
生产者消费者问题
资源池管理等场景