多线程程序设计(十一)——Thread-Specific Storage

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

◆ 适用场景

让每个线程拥有独立的变量实例,使该变量拥有与线程相同的生命周期。

◆ 解决方案

在声明变量时,用 thread_local 关键字进行标记。可被声明为 thread_local 的变量包括:命名空间范围内的变量、类的静态数据成员、局部变量。

◆ 参考实现

例子模拟了 3 个客户端(Client)线程通过日志器(Logger)将日志写入各自文件(*.log)中的过程。

class Logger
{

    ...

    static
    thread_local        #1
    ofstream
    __ofs__;

    ...

    void println(string message)
    {
        __ofs__ << "(" << &__ofs__ << ")" << message << endl;           #2
    }


    void close()
    {
        __ofs__.close();
    }

};

thread_local ofstream Logger::__ofs__(this_thread_id() + ".log");

Logger 对象包含日志文件的输出文件流。虽然文件流对象被神明为 static ,但由于加上了 thread_local 关键字(#1),则每条线程都会有一个文件流的对象实例,在 log 文件中会记录文件流对象的内存地址(#2)。

class Client
{

    ...
      
    Logger
    __logger__;

    ...    

    void run()
    {
        ...
        
        for (int i = 0; i < 10; ++i) {
            string msg;
            msg += "i = ";
            msg += std::to_string(i);
            __logger__.println(msg);
            std::this_thread::sleep_for(milliseconds(100));
        }
        __logger__.close();
        ...
    }

};

Client::run() 是客户端线程的入口函数,会调用 Logger 输出日志内容到 log 文件中。

int
main(int argc, char * argv[])
{

    ...

    Client alice("Alice");
    Client bobby("Bobby");
    Client chris("Chris");

    thread t1(&Client::run, &alice);
    thread t2(&Client::run, &bobby);
    thread t3(&Client::run, &chris);
    t1.join();
    t2.join();
    t3.join();

    ...
}

主程序中启动了 3 个客户端(Client)线程,并等待各线程输出日志结束。

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

class

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

sequence

◆ 验证测试

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

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

运行结果类似如下:

...
Alice(1992278848) BEGIN
Bobby(1983886144) BEGIN
Chris(1975493440) BEGIN
Alice(1992278848) END
Chris(1975493440) END
Bobby(1983886144) END
...

当前目录下也生成了 3 份 log 文件,与每条线程一一对应。

$ ls *.log
1975493440.log 1983886144.log 1992278848.log

$ head -n 1 *.log
==> 1975493440.log <==
(0x75bfa808)i = 0

==> 1983886144.log <==
(0x763fb808)i = 0

==> 1992278848.log <==
(0x76bfc808)i = 0

从 log 文件内容可以看到,不同的线程输出日志时用的输出文件流对象各不相同。

◆ 相关模式

◆ 最后

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

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

posted @ 2025-05-09 19:23  green-cnblogs  阅读(15)  评论(0)    收藏  举报