我在上篇博客C++不是C/C++ 提到C++的std::function给C++带来了闭包。有人留言说:“没有闭包真的很痛苦吗?不见得吧!”。我的回答是:“是的,真的很痛苦。”
首先来看闭包的两个作用:1.打包上下文 2.延迟调用。我们来设计一个linux 线程池来说明闭包的价值。
linux上线程创建的接口是:
int pthread_create(pthread_t*, const pthread_attr_t*, void *(*)(void *), void *params);
后面两个参数分别是一个函数指针,以及传给该函数的参数打包结构体的指针。上下文只能通过params结构体打包传入,以下代码只做使用说明,不保证正确性:
struct Params { int a; double b; }; void threadFunc(void* p) { struct Params* params = (struct Params*)(p); ...... } int f() { int a = 1; double b = 2; Params params; params.a = 1; params.b = 2; pthread_create(&id, NULL, (void *)threadFunc, ¶ms); }
线程执行的函数需要在内部将传入的params强制转换成真正的类型,才能获取所需要的参数。
假设我们设计一个C的线程池,那么,我无法预计扔到线程池里面的函数是什么类型的,也不知道它的参数是什么结构的,所以接口设计也需要同pthread_create一样,传入一个void*(*)(void*)的函数指
针以及一个void* 类型的参数打包。接口可能设计如下:
void schedule(struct ThreadPool* threadPool, void *(*)(void *)f, void *params)
好了,假设我们程序中有个函数
void Max(int a, int b, int* z) { *z = a > b? a : b; }
一个数组a[10000], 数组b[10000] ,数组c[i], 我要取得a[i] b[i]中较大者, 放入到c[i],代码可能如下(表明用法,不保证正确性):
struct MaxParam { int a; int b; int* c; }; void Max2(void* p) { struct MaxParam* param = (struct MaxParam*)(p); Max(param->a, param->b, param->c); } void g() { int a[10000]; int b[10000]; int c[10000]; for (int i = 0; i < 10000; ++i) { MaxParam p; p.a = a[i]; p.b = b[i];
p.c = &c[i];
schedule(&threadPool, (void *)Max2, &a); // 扔到线程池
}
wait(&threadPool); // 等待所有线程执行完成
}
可以看出,要重新定义一个新函数Max2和新的结构体MaxParam来配合线程池的使用, 代码中可能有千千万万的函数需要扔到线程池中执行,就要重新弄出千千万万个配合的函数和配合的结构体,
麻烦之极啊!
C++有了闭包会怎么做呢?接口就设计如下了:
class ThreadPool { void schedule(std::function<void (void)> func); void wait(); };
接口简单清晰,使用起来方便极了,(代码只表示用法,不保证正确性):
std::vector<int> x; std::vector<int> y; std::vector<int> z; for (int i = 0; i < 10000; ++i) { pool.schedule( std::bind(&Max, x[i], y[i], &z[i])); } pool.wait();
不用打包参数,也不需要搞个新函数配合线程池,用起来想出错真的很难啊!
C++一些复杂的设计是为了更加的简单!
可以看看boost threadpool 设计http://threadpool.sourceforge.net/,我写了一个例子如下
https://github.com/egametang/Egametang/blob/master/Cpp/Platform/Thread/ThreadPoolTest.cc