多线程程序设计(一)——Single Threaded Execution

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

◆ 适用场景

避免因多个线程同时访问一个共享数据而破坏数据的完整性。

◆ 解决方案

使用互斥元与 RAII(资源获取即初始化)相配合,保护临界区,保证同一时间点内仅有一条线程能访问共享数据。

◆ 参考实现

例子模拟 3 个人频繁地经过一扇只能允许一个人经过的门。当人通过门的时候,程序会打印出已经通过的人次数和当前人的信息。

class Gate
{

    ...

    mutable
    mutex                     #2
    __mtx__;


    void
    __check__()
    {
        if (__name__[0] != __address__[0])
            std::printf("%s\n", ("***** BROKEN *****" + to_string()).c_str());
        else
            std::printf("%s\n", ("******* OK *******" + to_string()).c_str());
    }

    
    ....


    void
    pass(string name, string address)               #1
    {
        lock_guard<mutex> guard(__mtx__);             #3
        ...
        __check__();                            #4
    }



    string
    to_string() const
    {
        ...
    }

};

一个人只能独自通过这扇门,这个过程被定义为临界区(#1),该区域由互斥元 __mtx__(#2)来保护。为简化在正常和异常情况下互斥元的上锁和开锁操作,用实现了 RAII 语法的 lock_guard 类模板来包覆(#3)。通过门的过程中,打印当前人员通过的信息(#4)。

class User
{

    ...    

    Gate &
    __gate__;

    ...

    void
    run()                       #1
    {
        std::printf("%s\n", (__name__ + " BEGIN").c_str());
        while (true) {                              #2
            __gate__.pass(__name__, __address__);
            std::this_thread::sleep_for(milliseconds(std::rand() % 1000));
        }
    }

};

...

int
main(int argc, char * argv[])
{
    ...
    
    Gate gate;
    User alice("Alice", "Alaska", gate);
    User bobby("Bobby", "Brazil", gate);
    User chris("Chris", "Canada", gate);

    thread t1(&User::run, &alice);
    thread t2(&User::run, &bobby);      #3
    thread t3(&User::run, &chris);

    t1.join();
    t2.join();                  #4
    t3.join();

    ...
}

User::run() 作为线程的初始函数(#1),模拟人频繁地通过门(#2)。在主线程中启动这 3 个人的子线程(#3)后,等待子线程完成(#4)。

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

class

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

sequence

◆ 验证测试

笔者在实验环境一中编译代码(-std=c++11)成功后运行可执行文件,

$ g++ -std=c++11 -lpthread single_threaded_execution.cpp 
$ ./a.out

运行结果如下:

Alice BEGIN
Bobby BEGIN
Chris BEGIN
*****   OK   ***** No.1: Bobby, Brazil
*****   OK   ***** No.2: Alice, Alaska
*****   OK   ***** No.3: Chris, Canada
*****   OK   ***** No.4: Bobby, Brazil
*****   OK   ***** No.5: Chris, Canada
*****   OK   ***** No.6: Chris, Canada
*****   OK   ***** No.7: Alice, Alaska
*****   OK   ***** No.8: Bobby, Brazil
*****   OK   ***** No.9: Chris, Canada
*****   OK   ***** No.10: Bobby, Brazil
*****   OK   ***** No.11: Alice, Alaska
*****   OK   ***** No.12: Bobby, Brazil
*****   OK   ***** No.13: Chris, Canada
*****   OK   ***** No.14: Bobby, Brazil
*****   OK   ***** No.15: Alice, Alaska
*****   OK   ***** No.16: Alice, Alaska
...

可以看到三个线程交替地、顺序地打印出已经通过的次数和当前线程信息。

◆ 相关模式

  • 待访问的共享数据不会改变时,可使用 Immutable 模式。
  • 想要分离“读”线程和“读”线程,可以使用 Read-Write Lock 模式。

◆ 最后

完整的代码请参考 [gitee] cnblogs/18743090

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

posted @   green-cnblogs  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
点击右上角即可分享
微信分享提示