本文摘要了《Java多线程设计模式》一书中提及的 Balking 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。
◆ 适用场景
当线程访问的共享数据没有准备好时,就放弃后续的操作。
◆ 解决方案
在临界区中判断共享数据是否准备好。如果没有准备好,就直接退出;如果准备好了,则继续后续操作。线程进入临界区之前,要在 std::mutex 上获取锁。
◆ 参考实现
例子模拟了数据写到文件中的过程。Saver 模拟了定期保存文件的行为,Changer 模拟了不定期修改文件并要求保存文件的行为。文件作为共享资源,由 Data 封装对其的操作,在不定期地被保存之前,会判断文件内容是否被修改。如果有修改,则执行保存操作;如果没有修改就直接退出。
class Data
{...bool__changed__;mutex__mtx__;void__do_save__(){...}...voidchange(string newcontent) #1{lock_guard<mutex> lk(__mtx__); #4...__changed__ = true;}voidsave() #2{lock_guard<mutex> lk(__mtx__); #4if (!__changed__) #3return;__do_save__();__changed__ = false;}};
Data 作为了共享数据的封装类。如果有新的内容需要修改,则先通过 change 函数(#1) 把内容暂存下来。如果有更新的内容被要求保存,则 change 函数被再次调用,更新的内容会暂存,而之前被暂存的内容被放弃;如果没有更新的内容被要求保存,则 save 函数(#2)会被调用,把最新的内存写入到文件中。对有无更新内容的判断(#3),就是 Balking 用到的警戒条件。由于 Data 的 change 函数和 save 函数是临界区,因此在对共享资源进行更新操作前,当前线程需要获取锁(#4),以保证文件的完整性。
class Saver
{...Data &__data__;...voidrun(){...while (true) {std::this_thread::sleep_for(milliseconds(1000)); #1__data__.save();}}};class Changer
{...Data &__data__;... voidrun(){...for (int i = 0; true; ++i) {...__data__.change(s);std::this_thread::sleep_for(milliseconds(std::rand()%1200)); #2__data__.save();}}};
Saver 每隔 1 秒(#1)会执行存储数据的操作;Changer 则在 0 ~ 2 秒间隔内之内不定期修改文件并要求存储数据。
以下类图展现了代码主要逻辑结构,
以下顺序图展现了线程并发中的交互。
◆ 验证测试
笔者在实验环境一中编译代码(-std=c++11)成功后运行可执行文件,
$ g++ -std=c++11 -lpthread balking.cpp
$ ./a.out
运行结果如下:
...
Saver: 1984136272
Changer: 1992528976
1984136272 calls __do_save__, contents = No.0
1992528976 calls __do_save__, contents = No.1
1992528976 calls __do_save__, contents = No.2
1984136272 calls __do_save__, contents = No.3
1984136272 calls __do_save__, contents = No.4
1984136272 calls __do_save__, contents = No.5
1984136272 calls __do_save__, contents = No.6
1992528976 calls __do_save__, contents = No.7
1984136272 calls __do_save__, contents = No.8
1992528976 calls __do_save__, contents = No.9
1984136272 calls __do_save__, contents = No.10
1984136272 calls __do_save__, contents = No.11
...
可以看到被写到文件中的内容没有出现被“重复保存”的情况。
◆ 相关模式
- Guarded Suspension 模式在警戒条件不成立时,线程会等待。
◆ 最后
完整的代码请参考 [gitee] cnblogs/18784993 。
致《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。