如何将同步调用转换成异步调用

本文探讨了同步与异步调用的概念及其在函数调用领域的应用,通过实例展示了如何将同步调用转换为异步调用,并讨论了异步调用在降低响应延迟方面的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    同步/非同步本来是IO方面的概念,不过我们可以把函数乃至各种RPC理解成一种IO设备,这样就可以把这两个词扩展到函数调用领域了。同步的函数调用指的是当函数调用返回的时候,需要函数干的事已经完成。而异步调用则没有这个保证,工作可能在函数返回前完成,也可能在之后某个时刻。
    同步/异步很容易跟阻塞/非阻塞混淆起来。严格说起来它们是有区别的:阻塞的调用指的是当前提满足的时候干活,不满足的时候等到满足为止;非阻塞则是前提满足的时候干活,不满足的时候立刻返回错误。不过很多时候,异步调用会表现出“非阻塞”的特征——不是真正的非阻塞,但由于很快返回(一般小于1us),所以在某种程度上可以被视为非阻塞(耗时短)的调用。在很多接口描述文档里,非阻塞其实说的是异步。在函数调用领域,一般情况下没有必要去严格区分异步和非阻塞。
    说来奇怪,我们天生就懂异步——比如你问隔壁几点的时候,不会两眼无神,整个人都斯巴达,等到回答后才恢复神智。但是编写/使用异步函数却会让大多数程序员感到痛苦不堪。
    为什么我们需要异步调用这个反人类的东西?本质原因在于有时候我们需要用少数线程来执行多个并行route,同时又要求低响应延迟。对于熟悉图形界面开发的人来说,应该都知道不能在事件响应函数里执行长耗时的任务,否则在函数执行完之前,整个界面将失去响应。这是因为大多数图形界面sdk都是用一个线程(为什么不用多个线程?还真有人试过,好像记得是早期的AWT,结果满地找牙。现在公认的最佳实践就是用唯一线程)来处理各种输入,产生事件并调用响应函数。如果这个线程在进行某个长时间的操作,界面失去响应就不足为奇了。又比如select/epoll/iocp服务器,采用这些API的服务,一个网络线程往往同时服务成千上万个连接。如果某个连接上的处理花费了过长时间,则这段时间内其他连接也同样无法得到响应。客户端界面失去响应1-2s,一般还是可以接受的,但服务器(比如网游)就不大好说了。所以需要避免在事件处理线程中执行会阻塞或者耗时过长的操作。为了这个目的,将同步调用转换成异步调用是最常用手段了。
    其实同步调用与异步调用很容易进行转换:如果想要把一个同步调用转换成异步调用,只需要一个线程或一组线程加上消息队列即可。根据异步调用处理工作完成的方式,可以将其细分成三类:1)甩手掌柜,不管;2)做个标记,让调用者需要时询问。“帮我买个馒头”- “馒头买好了没有?”- “馒头买好了没有?”3)直接回调,如“帮我买个馒头,买好后放在我包里”。下面的代码演示了如何将一个同步调用转换成后两类异步调用(第一类太没难度,不够niubility,忽略)
===================体位2的分隔线=====================
#include <iostream>
#include <string>
#include <queue>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <windows.h>    //    只用到Sleep函数

using namespace std;

string somefuncMaybeBlock(){
    string str;
    getline(cin, str);
    return str;    //    如果是未采用隐式数据共享的std实现,这句话比较低效,但无妨,此例只是演示体位变换
}
class CheckMeLaterCaller{
public:
    class Result{
    public:
        Result()
            : ready_flag(false){
        }
        void setData(const string& res){
            boost::mutex::scoped_lock lock(mtx);
            result = res;
            ready_flag = true;
        }
        string getData(){
            boost::mutex::scoped_lock lock(mtx);
            return result;
        }
        bool ready(){
            boost::mutex::scoped_lock lock(mtx);
            return ready_flag;
        }
    private:
        string result;
        bool ready_flag;
        boost::mutex mtx;
    };
    void call(Result* result){
        boost::mutex::scoped_lock(mtx);
        queue.push(result);
        if (queue.size() == 1){
            cond.notify_all();
             
    }
    CheckMeLaterCaller(){
        running_flag = true;
        grp.create_thread(boost::bind(&CheckMeLaterCaller::run, this));
    }
    ~CheckMeLaterCaller(){
        {
            boost::mutex::scoped_lock lock(mtx);
            running_flag = false;
            cond.notify_all();
        }
        grp.join_all();
    }
private:
    void run(){
        while (true){
            Result* res = NULL;
            {
                boost::mutex::scoped_lock lock(mtx);
                while (running_flag && queue.empty()){
                    cond.wait(lock);               
                }
                if (!running_flag){
                    return;
                }
                res = queue.front();
                queue.pop();
            }
            if (res){
                string tmp = somefuncMaybeBlock();
                res->setData(tmp);
                     
        }
         
    boost::mutex mtx;
    boost::condition_variable cond;
    std::queue<Result*> queue;
    bool running_flag;
    boost::thread_group grp;
};
int main(void) {
    CheckMeLaterCaller::Result rs[5];
       
    CheckMeLaterCaller cal;
    cal.call(&rs[0]);
    cal.call(&rs[1]);
    cal.call(&rs[2]);
    cal.call(&rs[3]);
    cal.call(&rs[4]);

    for (int i = 0; i < 5; i ++){
        while (!rs[i].ready()) {
            ::Sleep(10);
        }
        cout << rs[i].getData() << endl;
     
    return 0;
}
===================体位3的分隔线=====================
#include <iostream>
#include <string>
#include <queue>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <windows.h>    //    只用到Sleep函数

using namespace std;

string somefuncMaybeBlock(){
    string str;
    getline(cin, str);
    return str;    //    如果是未采用隐式数据共享的std实现,这句话比较低效,但无妨,此例只是演示体位变换
}
class ProactiveCaller{
public:
    class DoneHandler
    {
    public:
        virtual void onFinish(const string& str) = 0;
        virtual ~DoneHandler(){}
    };
    void call(DoneHandler* handler){
        boost::mutex::scoped_lock(mtx);
        queue.push(handler);
        if (queue.size() == 1){
            cond.notify_all();
               
      
    ProactiveCaller(){
        running_flag = true;
        grp.create_thread(boost::bind(&ProactiveCaller::run, this));
    }
    ~ProactiveCaller(){
        {
            boost::mutex::scoped_lock lock(mtx);
            running_flag = false;
            cond.notify_all();
        }
        grp.join_all();
    }
private:
    void run(){
        while (true){
            DoneHandler* handler = NULL;
            {
                boost::mutex::scoped_lock lock(mtx);
                while (running_flag && queue.empty()){
                    cond.wait(lock);                
                }
                if (!running_flag){
                    return;
                }
                handler = queue.front();
                queue.pop();
            }
            if (handler){
                string tmp = somefuncMaybeBlock();
                handler->onFinish(tmp);
                       
        }
      
   
    boost::mutex mtx;
    boost::condition_variable cond;
    std::queue<DoneHandler*> queue;
    bool running_flag;
    boost::thread_group grp;    
};
class PrintHandler : public ProactiveCaller::DoneHandler{
public:
    PrintHandler(): cnt(0){}
    virtual void onFinish(const string& str){
        boost::mutex::scoped_lock lock(mtx);
        cout << str << endl;
        ++ cnt;
    }
    int getCnt(){
        boost::mutex::scoped_lock lock(mtx);
        return cnt;
    }
private:
    boost::mutex mtx;
    int cnt;
};
int main(void) {    
    PrintHandler handler;
    ProactiveCaller cal;
    for (int i = 0; i < 5; i ++){
        cal.call(&handler);
      
    while (handler.getCnt() != 5) {
        ::Sleep(10);
      
    return 0;
}

如何将同步调用转换成异步调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值