多线程程序设计(十二)——Active Object
本文摘要了《Java多线程设计模式》一书中提及的 Active Object 模式的适用场景,并针对书中例子(若干名称有微调)给出一份 C++ 参考实现及其 UML 逻辑图,也列出与之相关的模式。
◆ 适用场景
多个调用方采用异步方式使用一个非线程安全且耗时较多的操作。
◆ 解决方案
建立一个拥有独立线程的主动对象,它具有可接受异步消息、并按照需求返回处理结果的特征。主动对象要组合使用 Producer-Consumer、Thread-Per-Message 和 Future 模式。将调用方(作为 Producer)的函数调用转换为请求对象,放入请求队列中;针对每一个请求对象启动一个独立的线程(作为 Thread-Per-Message)来处理(作为 Consumer),并将结果放入 future 中,返回给用户。
◆ 参考实现
例子模拟了一个可以处理异步“创建字符串”消息和“显示字符串”消息的主动对象。
class Active_Object
{
    ...
    
    make_string(int, char)
    
    ...
    
    display_string(string)
    ...
};
Active_Object 主动对象是一个抽象类,定义了“创建字符串”消息和“显示字符串”消息的接口。
class Result
{
    ...
    
    get_value()
    {
        ...
    }
};
Result 类封装了异步处理的结果,角色相当于 Future
中的 Data。
class Servant
    : public Active_Object
{
    ...
        
    make_string(int count, char filler)
    {
        for (int i = 0; i < count; ++i)
            std::this_thread::sleep_for(milliseconds(std::rand() % 100));
        return std::make_shared<Result>(string(count, filler));
    }
    ...
    
    display_string(string str)
    {
        std::this_thread::sleep_for(milliseconds(std::rand() % 10));
        std::printf("%s\n", str.c_str());
    }
};
而 Servant 实现了 Active_Object 接口,提供了实际的处理过程。
class Method_Request
{
    ...
    Servant &
    _servant_;
    ...   
    virtual
    void
    execute()
    = 0;
};
class Make_String_Request
    : public Method_Request
{
    ...
    shared_ptr<Result>
    __result__;
    ...
    condition_variable
    __cv__;
    ...    
    execute()
    {
        ...
        __result__ = _servant_.make_string(__count__, __filler__);
        __cv__.notify_one();
    }
    shared_ptr<Result>
    get_result()
    {
        ...
        __cv__.wait(lk,
                    [this] {
                        return __result__ != nullptr;
                    });
        return __result__;
    }
};
class Display_String_Request
    : public Method_Request
{
    ...
  
    execute()
    {
        _servant_.display_string(__message__);
    }
};
Method_Request 代表了抽象化的请求接口。具体化的请求则是 Make_String_Request 和 Display_String_Request 类。
class Activation_Queue
{
    ...
    __MAX_METHOD_REQUEST__ = 100;
    vector<shared_ptr<Method_Request>>
    __requests__;
    ...
    condition_variable
    __cv__;
    void
    put_request(shared_ptr<Method_Request> request)
    {
        ...
        
        __cv__.wait(lk,
                    [this] {
                        bool full = __count__ == __MAX_METHOD_REQUEST__;
                        if (full)
                            std::printf("Request queue is full. Waiting...\n");
                        return !full;
                    });
        __requests__[__tail__] = request;
        ...
        
        __cv__.notify_all();
    }
    shared_ptr<Method_Request>
    take_request()
    {
        
        ...
        
        __cv__.wait(lk,
                    [this] {
                        bool idle = __count__ == 0;
                        if (idle)
                            std::printf("Request queue is empty. Waiting...\n");
                        return !idle;
                    });
        shared_ptr<Method_Request> request = __requests__[__head__];
        
        ...
        
        __cv__.notify_all();
        
        return request;
    }
};
Activation_Queue 的角色相当于 Producer-Consumer
中的 Channel,是存放 Method_Request 的地方,它实现了对请求队列(vector)数据的同步操作。
class Scheduler
{
    ...
    
    Activation_Queue
    __queue__;
    ...
    shared_ptr<Result>
    invoke(shared_ptr<Method_Request> request)
    {
        __queue__.put_request(request);
        shared_ptr<Result> result;
        Make_String_Request * msr;
        if ((msr = dynamic_cast<Make_String_Request *>(request.get()))) {
            result = shared_ptr<Result>(msr->get_result());
        } else {
            result = nullptr;
        }
        return result;
    }
    void
    run()
    {
        while (true) {
            shared_ptr<Method_Request> request = __queue__.take_request();
            request->execute();
        }
    }
};
Scheduler 是整个 Active Object 的核心内容。它包含 Activation_Queue,用来存储从 Proxy 转发来的 Method_Request。当其 invoke() 被调用后,队列中会多一个请求对象,并把异步 Method_Request 的 Result 返回给调用方(【注】该消息此时还没有被真正地执行)。Scheduler::run() 作为调度线程的入口,不停地从队列中获取异步消息并执行。
class Proxy
    : public Active_Object
{
    ...
    Servant
    __servant__;
    Scheduler
    __scheduler__;
    thread
    __runner__;
    ...
    Proxy()
        : __servant__(), __scheduler__(), __runner__()
    {
        __runner__ = thread(&Scheduler::run, &__scheduler__);
    }
    ~Proxy()
    {
        __runner__.join();
    }
    shared_ptr<Result>
    make_string(int count, char filler)
    {
        shared_ptr<Method_Request> request(new Make_String_Request(__servant__, count, filler));
        future<shared_ptr<Result>> f = std::async(std::launch::async,
                                                  &Scheduler::invoke,
                                                  &__scheduler__,
                                                  request);
        return f.get();
    }
    void
    display_string(string message)
    {
        shared_ptr<Method_Request> request(new Display_String_Request(__servant__, message));
        future<shared_ptr<Result>> f = std::async(std::launch::async,
                                                  &Scheduler::invoke,
                                                  &__scheduler__,
                                                  request);
    }
};
Proxy 作为整个 Active Object 对外的代理,负责启动调度线程、将函数调用(make_string、display_string)转变为消息对象(Make_String_Request、Display_String_Request),这里的做法相当于 Thread-Per-Message。对于需要获得结果的异步消息(make_string),则需要等待其结果(调用 future::get())后再返回。
class Active_Object_Factory
{
    ...
    
    create_active_object()
    {
        return shared_ptr<Active_Object>(new Proxy());
    }
};
Active_Object_Factory 用于创建 Active Object 对象。
class Maker_Client
{
    ...    
    Active_Object &
    __ao__;
    ...    
    run()
    {
        for (int i = 1; true; ++i) {
            shared_ptr<Result> r = __ao__.make_string(i % 72, __name__[0]);
            std::this_thread::sleep_for(milliseconds(std::rand() % 10));
            std::printf("%s: %s\n", __name__.c_str(), r->get_value().c_str());
        }
    }
};
class Display_Client
{
    
    ...
    Active_Object &
    __ao__;
    ...
    
    run()
    {
        for (int i = 1; true; ++i) {
            string s(__name__ + ": " + std::to_string(i));
            __ao__.display_string(s);
            std::this_thread::sleep_for(milliseconds(std::rand() % 200));
        }
    }
};
...
int
main(int argc, char * argv[])
{
    ...
    shared_ptr<Active_Object> ao = Active_Object_Factory::create_active_object();
    Maker_Client alice("Alice", *ao);
    Maker_Client bobby("Bobby", *ao);
    Display_Client chris("Chris", *ao);
    thread t1(&Maker_Client::run, &alice);
    thread t2(&Maker_Client::run, &bobby);
    thread t3(&Display_Client::run, &chris);
    t1.join();
    t2.join();
    t3.join();
    ...
}
Make_Client 和 Display_Client 分别是发出异步“创建字符串(make_string)”调用和“显示字符串(display_string)”调用的客户端。在主程序中,模拟了 2 个“创建字符串”的客户端和 1 个“显示字符串”的客户端。
以下类图展现了代码主要逻辑结构,

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

◆ 验证测试
笔者在实验环境一中编译代码(-std=c++11)成功后运行可执行文件,
$ g++ -std=c++11 -lpthread active_object.cpp
$ ./a.out
运行结果类似如下:
...
Chris: 29
Chris: 30
Chris: 31
Chris: 32
Bobby: BBBBBBBB
Chris: 33
Alice: AAAAAAAAA
Chris: 34
Chris: 35
Chris: 36
Chris: 37
Chris: 38
Chris: 39
Chris: 40
Chris: 41
Bobby: BBBBBBBBB
Chris: 42
Chris: 43
Chris: 44
...
从输出结果可以看到,来自 Alice 和 Bobby 的 “make_string” 和来自 Chris 的 “display_string" 的任务是交替进行的。而随着创建字符串越来越长,每次处理 "make_string" 的时间间隔越长,两次间隔之间 "display_string" 的次数也就越来越多。
◆ 相关模式
- 将方法调用转化为请求对象的部分,使用了 Thread-Per-Message 模式。
 - 同步访问请求临界区的部分,使用了 Producer-Consumer 模式。
 - 给调用方返回执行结果时,使用了 Future 模式。
 
◆ 最后
完整的代码请参考 [gitee] cnblogs/18877415 。更多模式请参考多线程程序设计。
致《Java多线程设计模式》的作者结城浩。写作中也参考了《C++并发编程实战》中的若干建议,致作者 Anthony Williams 和译者周全等。
受限于作者的水平,读者如发现有任何错误或有疑问之处,请追加评论或发邮件联系 green-pi@qq.com。作者将在收到意见后的第一时间里予以回复。 本文来自博客园,作者:green-cnblogs,转载请注明原文链接:https://www.cnblogs.com/green-cnblogs/p/18877415 谢谢!
                    
                
                
            
        
浙公网安备 33010602011771号