什么是回调函数?
在编程中,总是会听到回调函数这个字眼,但回调函数究竟是什么尼?,维基百科的解释是这样子的:
In computer programming, a callback, also known as a “call-after”[1] function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later point in time as in an asynchronous callback.
中文翻译就是:
在计算机编程中,回调(也称为“ call-after ”函数)是作为参数传递给其他代码的任何可执行代码。其他代码可以在给定时间回调(执行)参数。该执行可以像在同步回调中那样立即执行,也可以在异步回调中稍后的某个时间点执行。
下面我们举个栗子:
假设你有一台洗衣机和一台烘干机,你的衣服需要洗衣机洗完之后,再拿去烘干机中进行烘干,下面定义两个函数。
#include<iostream>
void washing()
{
std::cout<<"洗衣服"<<std::endl;
}
void drying()
{
std::cout<<"烘干衣服"<<std::endl;
}
int main()
{
washing();
drying();
return 0;
}
上面的步骤是先把衣服拿去洗,洗完后立马拿去烘干。但是这期间你需要守着洗衣机,因为洗完衣服才能立马拿去烘干。洗衣服时间一般需要30分钟,你不想一直盯着洗衣机30分钟,也没有人会想一直盯着洗衣机看30分钟,因为这样子很傻。
因此,接下来轮到回调函数出场了,接着举栗:
假设你有一台洗烘一体机,你的衣服洗完之后会自动进行烘干,函数定义如下:
#include<iostream>
typedef void (*callback)(void);
void drying();
void washing(callback func)
{
std::cout<<"洗衣服"<<std::endl;
func();
}
void drying()
{
std::cout<<"烘干衣服"<<std::endl;
}
int main()
{
washing(drying);
return 0;
}
大家可以发现,washing
比之前多了一个参数,这个参数是一个函数指针,并在洗完衣服之后执行该函数。在main函数中把drying
这个函数给传进去了,此时这个函数我们就称为callback,中文称之为回调。
这样子你就不用向之前那样盯着衣服洗完,再立马拿去烘干了,因为你把洗完衣服要做的事情告诉了washing
这个函数,这个函数执行完洗衣服这个动作后,会立马进行烘干。不过这个还是一个同步执行
的过程,因为在washing
没执行完之前,main
函数什么也做不了。
异步回调
洗衣服的时间那么长,我可以不可以利用洗衣服的时间做点其他事情尼,那么就要用到异步调用了,所以的异步就是不等任务执行完,直接执行下一个任务。异步回调 其实就是使用了异步调用的方式。
下面是使用异步调用的一段代码,我们有两个函数,其中washing
耗时5s,do_homework
函数耗时3s,然后我们在main
函数中对其进行异步调用,并且对整个main函数执行时间进行了记录,看看异步耗时需要多久。
#include<iostream>
#include <future>
#include <unistd.h>
#include <chrono>
typedef std::chrono::steady_clock STEADY_CLOCK;
typedef std::chrono::steady_clock::time_point TIME_POINT;
void print_diff_time(const TIME_POINT& t1, const TIME_POINT& t2)
{
std::chrono::duration<double, std::milli> dTimeSpan = std::chrono::duration<double,std::milli>(t2-t1);
std::cout << "time span : " << dTimeSpan.count() << "ms\n";
}
int washing()
{
std::cout<<"开始洗衣服"<<std::endl;
sleep(5);
std::cout<<"花了5秒衣服洗完了"<<std::endl;
return 0;
}
int do_homework()
{
std::cout<<"开始做作业"<<std::endl;
sleep(3);
std::cout<<"花了3秒作业做完了"<<std::endl;
return 1;
}
int main()
{
TIME_POINT start_time = STEADY_CLOCK::now();
std::cout<<"main function start"<<std::endl;
std::future<int> result = std::async(washing);
std::future<int> result2 = std::async(do_homework);
std::cout<<"continue......!"<<std::endl;
std::cout << result.get() << std::endl;
std::cout << result2.get() << std::endl;
std::cout << "main function finish!" << std::endl;
TIME_POINT end_time = STEADY_CLOCK::now();
print_diff_time(start_time, end_time);
}
执行结果如下:
main function start
continue......!
开始洗衣服
开始做作业
花了3秒作业做完了
花了5秒衣服洗完了
0
1
main function finish!
time span : 5000.86ms
若是同步调用,则需要耗时8s左右,通过异步调用我们可以执行到washing()
函数时迅速返回,继续执行下一个函数do_homework
,因此耗时只需要5s左右。
下面将上面的代码改成异步回调的方式,如下:
#include<iostream>
#include <future>
#include <unistd.h>
#include <chrono>
typedef std::chrono::steady_clock STEADY_CLOCK;
typedef std::chrono::steady_clock::time_point TIME_POINT;
typedef int (*callback)(void);
void print_diff_time(const TIME_POINT& t1, const TIME_POINT& t2)
{
std::chrono::duration<double, std::milli> dTimeSpan = std::chrono::duration<double,std::milli>(t2-t1);
std::cout << "time span : " << dTimeSpan.count() << "ms\n";
}
int washing(callback call_back)
{
std::cout<<"开始洗衣服"<<std::endl;
std::future<int> result = std::async(std::launch::async, call_back);
sleep(5);
std::cout<<"花了5秒衣服洗完了"<<std::endl;
std::cout << result.get() << std::endl;
return 0;
}
int do_homework()
{
std::cout<<"开始做作业"<<std::endl;
sleep(3);
std::cout<<"花了3秒作业做完了"<<std::endl;
return 1;
}
int main()
{
TIME_POINT start_time = STEADY_CLOCK::now();
std::cout<<"main function start"<<std::endl;
//第三个参数是washing的入参
std::future<int> result = std::async(std::launch::async, washing, do_homework);
std::cout<<"continue......!"<<std::endl;
std::cout << result.get() << std::endl;
std::cout << "main function finish!" << std::endl;
TIME_POINT end_time = STEADY_CLOCK::now();
print_diff_time(start_time, end_time);
}
执行结果如下:
main function start
continue......!
开始洗衣服
开始做作业
花了3秒作业做完了
花了5秒衣服洗完了
1
0
main function finish!
time span : 5000.88ms
在代码中,我们把do_homework
做作业这个函数作为参数传入washing
中,这样子我们可以在调用washing
的同时,do_homework
也会在内部异步调用,且不阻塞washing
内其他语句的执行,最后再将执行完成的结果返回到washing
中打印出来。
总结
最后,简单总结一下,同步的代码看起来比较直观,编码和维护都比较容易,但是效率低,效率低在绝大多数情况下都是不能忍受的。而异步效率高,但是通过callback这种形式逻辑容易被代码割裂,代码比较不直观。不过C++20已经有了协程,有兴趣的同学可以去研究一下,通过协程既可以实现异步,代码看起来也比较直观明了。
更多精彩内容,可以关注爱打码公众号,每月不定时更新。