Linux系统编程(20)——生产者-消费者模型基础示例(栈、队列实现、实现线程池)

生产者-消费者模型

多线程场景的的典型应用,应用场景非常广泛!手撕!!!

  • 消费者和消费者之间,是一个互斥关系。
  • 生产者和生产者之间,是一个互斥关系。
  • 生产者和消费者之间,是一个互斥同步关系。

注:同步互斥不一定非要用互斥锁和条件变量,还可以用信号量。


代码示例1:(栈实现,互斥锁/条件变量实现互斥同步)

#include <stdio.h>
#include<pthread.h> //头文件
#include <unistd.h>  //sleep头文件
#include <vector>

//定义一个互斥锁
pthread_mutex_t mutex;


//实现一个生产者消费者模型
//首先要有一个交易场所
std::vector<int> data;

//两个角色:生产者/消费者——两个线程
void* Product(void* arg) {
	(void*)arg;
	int count = 0;
	//负责把数据送到的交易场所中
	while (1)	{
		pthread_mutex_lock(&lock);
		data.push_back(++count);
		pthread_mutex_unlock(&lock);
		usleep(789789);
	}
	return NULL;
}

void* Consume(void* arg) {
	(void*)arg;
	//负责把交易场所中的数据获取出来
	while (1) {
		pthread_mutex_lock(&lock);
		//每次取最后一个元素
		//注意判空
		if (!data.empty()) {
			int result = data.back();
			data.pop_back();
			printf("result = %d\n", result);
		}
		pthread_mutex_unlock(&lock);
		usleep(123123);
	}
	return NULL;
}


int main() {

	pthread_mutex_init(&lock, NULL);//互斥锁初始化函数

	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, Product, NULL); //创建新线程
	pthread_create(&tid2, NULL, Consume, NULL);

	pthread_join(tid1,NULL); //线程等待
	pthread_join(tid2, NULL);


	pthread_mutex_destory(&lock);//互斥锁释放函数
	system("pause");
	return 0;
}

//不加互斥锁:
//但是使用过多线程,就有可能有段错误。
//因为C++STL中所提供的容器和算法都是线程不安全[重要]

//加上互斥锁:保证线程安全。<
<think>嗯,用户问的是多线程socket开发中发送数据是否需要加锁。这个问题挺典型的,涉及多线程编程的核心痛点——共享资源的安全访问。用户提供的几个引用挺有意思。[^1][^2]都明确提到了锁机制的重要性,特别是[^1]展示了可重入锁的使用场景,[^2]则对比了synchronizedReentrantLock。而[^4][^5]更贴近实际问题,展示了实际socket编程中线程创建临界区保护的代码片段。考虑从三个维度分析:1.socket描述符本身的线程安全性:内核文档里提到,send操作是原子性的,但仅针对单个数据包层面。这让我想到Linux内核里的sk_buff结构,虽然内核保证了单个send操作的原子性,但应用层连续调用send时仍然会有乱序问题。2.应用层数据状态的一致性:特别是发送结构化的协议数据时特别需要考虑。比如用户要发送一个HTTP报文,头body分开发送,中途被其他线程的发送操作插入就麻烦了。3.非阻塞模式下的特殊风险:引用[^3]里QT的例子里提到了读写冲突,实际上在非阻塞socket里,EWOULDBLOCK错误处理时更容易出现竞态条件。用户可能实际遇到的是这样一个场景:他们的服务端用线程池处理请求,每个线程都在往同一个客户端socket写数据。这种情况不加锁的话,日志信息都可能错乱,更不用说业务数据了。比较有意思的是引用[^5]里lwIP的例子,它特意提到"非线程安全需要加锁",这正好印证了嵌入式环境下更需要显式保护。虽然现代操作系统对基本socket操作有保护,但像连接状态变更这种复杂操作还是需要应用层锁。所以回复考虑聚焦在:-明确哪些情况必须加锁(比如共享socket、结构化协议)-指出可能不需要加锁的特殊情况(比如独立连接、纯读操作)-给出具体的锁选择建议(互斥锁vs读写锁)-最后总结最佳实践:能用线程私有socket最好,否则务必加锁用户可能还需要考虑锁粒度的问题。不过这个在他们没给出具体架构前暂时不提,以免信息过载。</think> 在多线程环境下进行 socket 开发时,**数据发送操作通常需要加锁保护**,但具体取决于线程对 socket 资源的访问方式。以下是关键分析: ### 🔒 一、必须加锁的情况 1. **共享同一个 socket** 若多个线程同时操作**同一个 socket 描述符**发送数据,必须加锁。否则会导致: - **数据竞争**:多个线程的发送操作可能交织导致数据混乱(如线程 A 的包头 + 线程 B 的包体)。 - **协议状态破坏**:例如 TCP 流中夹杂不完整的数据片段。 - **系统调用错误**:并发 `send()` 可能因缓冲区状态竞争引发 `EAGAIN` 或错误覆盖(引用[^4][^5])。 ```python # 伪代码:多线程共享 socket 需加锁 lock = threading.Lock() def send_thread(data): with lock: # 保证 send 操作的原子性 sock.send(data) ``` 2. **操作共享协议状态** 若发送逻辑涉及协议头生成、分片组装等**共享状态修改**(如更新序列号),需保护临界区(引用[^5])。 ### 🔓 二、无需加锁的情况 1. **线程独立 socket** 每个线程持有**专属的 socket 连接**(如线程池中各线程维护独立连接),数据发送无需加锁(引用[^4])。 ```c // 引用[^4]代码片段:每个线程独立处理连接 hSendThread = _beginthreadex(NULL, 0, SendMsg, (void*)&sockCli, 0, NULL); ``` 2. **纯读取操作** 若多个线程仅读取 socket 数据而**不修改连接状态**,可放宽限制(但接收缓冲区的解析仍需锁)。 ### 💡 三、建议的解决方案 1. **互斥锁(Mutex)** 直接保护 `send()` 调用,如 Python 的 `threading.Lock` 或 Java 的 `ReentrantLock`(引用[^1][^2])。 2. **连接池化** 为每个线程分配独立 socket,消除资源竞争(引用[^4]示例)。 3. **无锁队列(生产-消费者模型)** ```c // 主线程负责发送,工作线程提交数据到队列 void worker_thread() { enqueue(send_queue, data); // 无锁队列操作 } void sender_thread() { while (data = dequeue(send_queue)) { sock.send(data); // 单线程发送 } } ``` ### ⚠️ 四、常见误区 - **"`send()` 是原子操作"**:虽然单次 `send()` 调用是原子的,但多次调用间的逻辑非原子(如循环发送大文件)。 - **"非阻塞 socket 不需要锁"**:非阻塞模式仍需锁保证协议逻辑一致性(引用[^5])。 ### 📌 总结 | 场景 | 是否需要加锁 | 理由 | |-------------------------------|--------------|--------------------------| | 多线程共享同一 socket 发送 | ✅ 必须 | 避免数据交织状态破坏 | | 多线程操作独立 socket | ❌ 不需要 | 无共享资源竞争 | | 发送逻辑依赖共享协议状态 | ✅ 必须 | 防止状态更新冲突 | > **最佳实践**:优先采用**线程专属 socket** 或 **单发送线程+队列**模式(引用[^4]),若必须共享 socket,务必使用互斥锁(如引用[^1]的 `RLock` 或引用[^2]的 `ReentrantLock`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值