多线程程序设计(十一)——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)线程,并等待各线程输出日志结束。
以下类图展现了代码主要逻辑结构,

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

◆ 验证测试
笔者在实验环境一中编译代码(-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 文件内容可以看到,不同的线程输出日志时用的输出文件流对象各不相同。
◆ 相关模式
- 要对多数的线程进行共享互斥时,要使用 Single Threaded Execution 模式。
 
◆ 最后
完整的代码请参考 [gitee] cnblogs/18868146 。更多模式请参考多线程程序设计。
致《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。
受限于作者的水平,读者如发现有任何错误或有疑问之处,请追加评论或发邮件联系 green-pi@qq.com。作者将在收到意见后的第一时间里予以回复。 本文来自博客园,作者:green-cnblogs,转载请注明原文链接:https://www.cnblogs.com/green-cnblogs/p/18868146 谢谢!
                    
                
                
            
        
浙公网安备 33010602011771号