libuv的线程池,即工作队列

1. 在使用libuv的时候,发现使用其提供的 uv_queue_work 的确可为用户的某一任务放在单独的线程中执行。但是只能同时执行4个(可以修改 UV_THREADPOOL_SIZE 环境变量设置)。

2. 介绍一下libuv的工作队列,http://www.nowx.org/uvbook/threads.html#libuv

libuv 工作队列

uv_queue_work() 是一个辅助函数, 它可以使得应用程序在单独的线程中运行某一任务, 并在任务完成后触发回调函数. uv_queue_work 看似简单, 但是在某些情况下却很实用, 因为该函数使得第三方库可以以事件循环的方式在你的程序中被使用.当你使用事件循环时, 应该 确保在事件循环中运行的函数执行 I/O 任务时不被阻塞, 或者事件循环的回调函数不会占用太多 CPU 的计算能力. 因为一旦发生了上述情况, 则意味着事件循环的执行速度会减慢, 事件得不到及时的处理.

但是也有一些代码在线程的事件循环的回调中使用了阻塞函数(例如执行 I/O 任务), (典型的 ‘one thread per client’ 服务器模型), 并在单独的线程中运行某一任务. libuv 只是提供了一层抽象而已.

下面是一个简单的例子(原型为 node.js is cancer). 我们程序计算 fibonacci 数, 中途也会休眠一会,但是由于是在单独的线程中运行的, 所以阻塞和 CPU 计算密集型的任务(fibonacci 数的计算)并不会阻碍事件循环执行其它任务.

queue-work/main.c - lazy fibonacci

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void fib(uv_work_t *req) {
    int n = *(int *) req->data;
    if (random() % 2)
        sleep(1);
    else
        sleep(3);
    long fib = fib_(n);
    fprintf(stderr, "%dth fibonacci is %lu\n", n, fib);
}

void after_fib(uv_work_t *req, int status) {
    fprintf(stderr, "Done calculating %dth fibonacci\n", *(int *) req->data);
}

真正的执行任务的函数比较简单, 只是在单独的线程中执行. uv_work_t 结构是线索, 你可以通过 void* date 传递任意数据, 并且利用该指针和执行线程通信(数据传递). 但注意, 如果在多个线程中同时修改某个变量, 就应该使用合适的锁来保护共享变量.

触发器 是 uv_queue_work:

queue-work/main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main() {
    loop = uv_default_loop();

    int data[FIB_UNTIL];
    uv_work_t req[FIB_UNTIL];
    int i;
    for (i = 0; i < FIB_UNTIL; i++) {
        data[i] = i;
        req[i].data = (void *) &data[i];
        uv_queue_work(loop, &req[i], fib, after_fib);
    }

    return uv_run(loop, UV_RUN_DEFAULT);
}

线程函数会在单独的线程中被启动, 并传入 uv_work_t 结构, 一旦函数返回, 就会调用 after_fib 函数, 同时也传入 uv_work_t 结构的指针.

3. 下面是完整代码

#include 
#include 
#include 
#include 

#define FIB_UNTIL 10
uv_loop_t *loop;

long fib_(long t)
{
    if (t == 0 || t == 1)
        return 1;
    else
        return fib_(t-1) + fib_(t-2);
}

void fib(uv_work_t *req)
{
    int n = *(int *) req->data;
    fprintf(stderr, "%dth fibonacci\n", n);
    int i=0;
    if (random() % 2)
    {
        i=5;
        sleep(5);
    }
    else
    {
        i=10;
        sleep(10);
    }

    long fib = fib_(n);
    fprintf(stderr, "%dth fibonacci is %lu---costTime %ds\n", n, fib, i);
}

void after_fib(uv_work_t *req, int status)
{
//    fprintf(stderr, "Done calculating %dth fibonacci\n", *(int *) req->data);
}

int main()
{
    loop = uv_default_loop();

    int data[FIB_UNTIL];
    uv_work_t req[FIB_UNTIL];
    int i;
    for (i = 0; i < FIB_UNTIL; i++)
    {
        data[i] = i;
        req[i].data = (void *) &data[i];
        uv_queue_work(loop, &req[i], fib, after_fib);
    }

    return uv_run(loop, UV_RUN_DEFAULT);
}

输出:(整理后的,实际输出不是这样,后面的 "------------t" 表示经过的时间)

0th fibonacci-----------0
1th fibonacci-----------0
2th fibonacci-----------0
3th fibonacci-----------0
4th fibonacci-----------5
5th fibonacci-----------5
6th fibonacci-----------5
7th fibonacci-----------10
8th fibonacci-----------10
9th fibonacci-----------10

0th fibonacci is 1---costTime 5s-----------5
2th fibonacci is 2---costTime 5s-----------5
3th fibonacci is 3---costTime 5s-----------5
1th fibonacci is 1---costTime 10s-----------10
4th fibonacci is 5---costTime 5s-----------10
5th fibonacci is 8---costTime 5s-----------10
6th fibonacci is 13---costTime 10s-----------15
8th fibonacci is 34---costTime 5s-----------15
9th fibonacci is 55---costTime 5s-----------15
7th fibonacci is 21---costTime 10s-----------20


说明:发现开始的时候(0秒的时候)只提交了4个,由于5秒后完成了3个,所以又提交了3个,10秒完成了3个然后又提交了3个,也就是,每次运行的线程总数不超过4个,这样看来,libuv的工作队列制约了并行计算的能力,但是这样更加安全,不容易因为不可控的线程数量导致程序崩溃,事实上libuv为我们提供了4个线程的线程池。所以有的线程比较耗时或阻塞了,就会使可用线程数减少。


你可能感兴趣的:(Libuv)