多线程程序设计(四)——Balking

本文摘要了《Java多线程设计模式》一书中提及的 Balking 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。

◆ 适用场景

当线程访问的共享数据没有准备好时,就放弃后续的操作。

◆ 解决方案

在临界区中判断共享数据是否准备好。如果没有准备好,就直接退出;如果准备好了,则继续后续操作。线程进入临界区之前,要在 std::mutex 上获取锁。

◆ 参考实现

例子模拟了数据写到文件中的过程。Saver 模拟了定期保存文件的行为,Changer 模拟了不定期修改文件并要求保存文件的行为。文件作为共享资源,由 Data 封装对其的操作,在不定期地被保存之前,会判断文件内容是否被修改。如果有修改,则执行保存操作;如果没有修改就直接退出。

class Data
{

    ...

    bool
    __changed__;


    mutex
    __mtx__;


    void
    __do_save__()
    {
        ...
    }

    ...

    void
    change(string newcontent)                       #1
    {
        lock_guard<mutex> lk(__mtx__);      #4
        ...
        __changed__ = true;
    }


    void
    save()                                          #2
    {
        lock_guard<mutex> lk(__mtx__);        #4
        if (!__changed__)               #3
            return;
        __do_save__();
        __changed__ = false;
    }

};

Data 作为了共享数据的封装类。如果有新的内容需要修改,则先通过 change 函数(#1) 把内容暂存下来。如果有更新的内容被要求保存,则 change 函数被再次调用,更新的内容会暂存,而之前被暂存的内容被放弃;如果没有更新的内容被要求保存,则 save 函数(#2)会被调用,把最新的内存写入到文件中。对有无更新内容的判断(#3),就是 Balking 用到的警戒条件。由于 Data 的 change 函数和 save 函数是临界区,因此在对共享资源进行更新操作前,当前线程需要获取锁(#4),以保证文件的完整性。

class Saver
{

    ...

    Data &
    __data__;

    ...

    void
    run()
    {
        ...
        while (true) {
            std::this_thread::sleep_for(milliseconds(1000));    #1
            __data__.save();
        }
    }

};


class Changer
{

    ...

    Data &
    __data__;

    ...    

    void
    run()
    {
        ...
        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 秒间隔内之内不定期修改文件并要求存储数据。

以下类图展现了代码主要逻辑结构,

class

以下顺序图展现了线程并发中的交互。

sequence

◆ 验证测试

笔者在实验环境一中编译代码(-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
...

可以看到被写到文件中的内容没有出现被“重复保存”的情况。

◆ 相关模式

◆ 最后

完整的代码请参考 [gitee] cnblogs/18784993 。更多模式请参考多线程程序设计

《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。

posted @ 2025-03-21 20:45  green-cnblogs  阅读(11)  评论(0)    收藏  举报