缓冲区设计—线程间通讯

本文详细介绍了如何使用线程锁和条件变量设计一个线程安全的缓冲区,解决线程间的等待和通知问题。通过Mutex和Cond类实现资源的生产和消费,确保push和get操作的正确同步。文章提供了具体的C++代码实现,并展示了测试程序,但测试程序未完全验证数据的正确性。下篇将探讨进程间通讯的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

书接上回。

上一篇里,我们设计并实现了环形队列的数据结构。同时留下了下面的更具复杂性的问题:

  • 对于push的操作,需要操作前等待队列已经有了空间,也就是说队列没有满的状态。等到这个状态出现了,才继续进行push的操作,否则,push操作挂起。
  • 对于get 的操作,需要操作前等待队列有了数据,也就是说队列不为空的状态。等到这个状态出现了,才继续进行get的操作,否则,get操作挂起。
  • 上面的push操作/get操作一般是不同的进程和线程。同时,也有可能有多个push的操作和get操作的进程和线程。

以上分析,可以看到,push和get都有一个等待满足条件的等待状态。

  • push等待的条件是队列为有空闲的状态,当get操作后,就会腾出队列空间,从而可能满足push的条件。那么,get操作后,就需要通知push,让push从等待转入测试判断,如果条件满足,则会进行push的操作。
  • 另一方面,get等待的条件是队列为有数据的的状态,当put操作后,就会将数据插入到队列中,从而可能满足get的条件。那么,push操作后,就需要通知get,让get从等待转入测试判断,如果条件满足,则会进行get的操作。

上面的push和get的等待问题都可以归结为资源生产和消费的问题。

 

 

设资源数为nresource。

 

资源生产方进行下面的流程:

  • 资源生产操作;
  • nresource加一;
  • 通知资源消费方。

资源消费方则为下面的流程:

  • 等待来自于生产方的通知;
  • 判断是否资源数 nresource大于零,如果条件不满足,回到上一条;
  • 进行资源消费的操作;
  • nresource减一。

这里,这个资源生产和消费的核心技术是等待和通知技术。使用System V的信号灯以及Posix的信号灯技术可以实现这个功能。在这里,我选择另一个更高效的线程锁和条件变量的技术来实现。

 

具体的实现的程序的架构:

定义变量:
pthread_mutex_t mutex;
pthread_cond_t cond;
int nresouce;
进行变量的初始化,nresource设置为资源的初始数目。例如,对于队列的put操作,其初始值为队列的大小,而对于队列的get操作,初始值应为0。

生产方:
pthread_mutex_lock(&mutex);
produce_resource(); // 产生资源
nresource++;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

消费方:
pthread_mutex_lock(&mutex);
while ( nresource < 1 )
pthread_cond_wait(&cond, &mutex);
consume_resource(); // 消费资源
nresource--;
pthread_mutex_unlock(&mutex);


  • pthread_mutex_lock及pthread_mutex_unlock是一对加锁和解锁的过程,用于界定临界区范围。
  • pthread_cond_signal会唤醒在同样的参数cond下调用pthread_cond_wait的休眠的线程。
  • 显然,pthread_cond_wait是使线程进入休眠状态,等待来自于pthread_cond_signal的唤醒。它们靠同样的参数cond联系在一起。

在我们这个实际的环形队列的方案中,push及get实际上是互相的生产者和消费者。push为get生产队列数据,get消费队列数据;而get为push生产队列空间的资源,push消费队列空间资源。

### 线程间通信的方式及实现方法 线程间通信是指在一个进程中不同线程之间交换数据或协调操作的过程。为了确保线程能够安全有效地协作,通常会使用多种技术来实现这一目标。 #### 一、全局变量方式 一种简单直接的线程间通信方式是利用共享内存区域中的全局变量[^2]。在这种模式下,多个线程可以访问同一个全局变量并对其进行读写操作。然而,这种方式存在潜在的风险——如果缺乏必要的同步措施,则可能导致竞态条件(race condition),从而引发不可预测的行为。因此,在实际应用中需配合互斥锁或其他同步手段加以保护。 #### 二、消息传递方式 另一种常见的做法是借助消息队列完成跨线程的信息交过程。具体来说,源端线程负责构建携带所需指令的消息对象并将之投递给目的端;后者则从接收缓冲区提取待处理项进而执行相应动作。Android平台上的Handler机制正是基于此理念设计而成:它允许开发者通过Message实例封装异步事件,并交由关联的目标Looper组件安排后续处置程[^3]。 #### 三、参数传递方式 对于某些特定场合下的短距离调用需求,也可以考虑采用函数指针形式作为中介桥梁连接发起方与响应方两者之间的联系通道。不过这种方法适用范围较为局限,更多时候仅限于局部范围内临时性的交互行为。 #### 四、阻塞队列(Blocking Queue) 当面临诸如生产者-消费者这类典型应用场景时,“阻塞队列”无疑成为首选方案之一[^4]。Java标准库提供了专门用于解决此类问题的数据结构类型java.util.concurrent.BlockingQueue<T>及其若干具体实现类(如ArrayBlockingQueue, LinkedBlockingDeque等)。这些容器支持原子级别的存取操作,能够在必要时刻自动挂起试图违反容量约束的操作请求直到满足前提为止,极大地简化了开发人员编写并发逻辑的工作量。 #### 五、等待通知机制(wait & notify/notifyAll) 除了上述提到的各种显式传输途径之外,还有一种隐含式的协同策略值得我们关注 – 即依靠内置监视器提供的基本功能达成有限度内的互动效果[^5]。例如Object.wait(), Object.notify()以及Object.notifyAll()这三个核心API共同构成了基础版的对象级信号灯体系。需要注意的是,这里所说的“解锁”并非真正意义上的解除锁定状态而是仅仅给予其他候选竞争者获得进入临界区资格的机会而已。 #### 六、计数倒闸门(CountDownLatch) 最后介绍一个高级别的抽象工具 - java.util.concurrent.CountDownLatch 。它可以用来表示某个固定数量的任务集合完成后才会触发下一步骤的前提条件。每个参与其中的合作单元只需调用countDown() 方法减少当前剩余预期值直至归零便会激活处于await() 阻塞状态下守候的所有观察者继续前进。 ```python import threading from queue import Queue def producer(queue): for i in range(5): print(f"Producing {i}") queue.put(i) def consumer(queue): while True: item = queue.get() if item is None: break print(f"Consuming {item}") queue = Queue(maxsize=3) t1 = threading.Thread(target=producer, args=(queue,)) t2 = threading.Thread(target=consumer, args=(queue,)) t1.start(); t2.start() # Stop the consumer after all items are consumed. queue.join() queue.put(None) t2.join() ``` 以上代码展示了如何在线程之间使用`Queue`来进行简单的生产者-消费者的通信
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值