第6-7部分 事件处理与条件变量
现在我们需要设计一个网络应用,这个应用需要完成如下任务:
- 和服务器交互
- 从XML文件中加载一些数据
- 处理第2个任务中得到的数据
显然任务1不依赖于其他两个任务,而任务3依赖于任务2。如果这个应用只有一个线程,那么任务2中读取XML文件的操作会拖慢应用的速度。为了提高性能,我们可以将任务2设计成单独的子线程,而任务1和3设计成主线程。具体来说,线程1负责:
- 和服务器交互
- 等待XML数据被线程2加载
- 收到数据就作数据处理
而线程2负责:
- 加载XML数据
- 告知线程1数据是否完成
可以从下图直观的看出程序的执行流程:
图中线程1需要等待线程2发送完成数据读取
的信号。一旦收到信号,便会执行数据处理操作。这里,多线程的好处在于,当线程1忙于与服务器交互的时候,线程2可以同步加载数据,这样就减小了总的等待时间。那么如何实现这个程序呢?
第一种方法
最简单的想法是自定义一个全局变量,作为线程2是否完成数据读取的信号。初始化这个变量为false
,当线程2完成数据读取就设置为true
。线程1轮询这个变量,当读到这个变量为true
时,就处理数据。但是由于这个变量被两个线程共用,因此需要使用互斥锁来进行线程同步。具体代码如下:
1 |
|
输出如下:
然而这种做法存在缺陷:
反复的上锁和解锁会消耗大量不必要的时间。有没有更好的解决方法呢?
第二种方法
为了杜绝反复的轮询,我们希望线程1能够在事件未发生时阻塞,只有当事件发生后才继续。这种想法可以通过条件变量
实现。
条件变量是一种用于两个线程交互的变量。一个线程等待它被激活,另一个线程则负责激活它。
使用条件变量时需要添加头文件:
1 |
让我们看一下条件变量如何工作:
- 首先定义一个互斥锁,用于条件变量的工作。
- 线程1调用条件变量的
wait()
函数,这个函数在其内部查询互斥锁,检查是否满足了指定的条件。 - 如果没有满足指定条件,条件变量释放这个互斥锁并阻塞线程1。
- 线程2负责在指定条件满足是,将条件变量激活。
- 一旦条件变量激活,线程1恢复运转。它首先检查指定条件是否真正满足(因为一个更高等级的调用也可能使线程1恢复运转)。
- 如果是一个更高级的调用,那么线程1再次调用
wait()
函数。
具体的代码实现如下:
1 |
|