`

libuv介绍--多线程

阅读更多
原文:http://nikhilm.github.com/uvbook/threads.html

libuv的线程功能的值得注意的方面是它是一个libuv内部自包含的部分。然而其它的特性密切依赖事件循环和回调原则,线程是完全不知的,他们按需求阻塞,信号错误直接通过返回值和,如第一个例子所示,甚至不需要一个运行的事件循环。

libuv的线程API也非常有限,因为在所有平台上线程的语义和语法都是不同的,和不同级别的完整性。

这一章作如下假设:只有一个事件循环,运行在一个线程(主线程)。没有其他线程与事件循环互动(除了使用uv_async_send)。多事件循环包括运行事件循环在不同的线程和管理它们。

核心线程操作

这里没有太多,你只是用 uv_thread_create()启动一个线程,使用uv_thread_join()等待它关闭。

thread-create/main.c

int main() {
    int tracklen = 10;
    uv_thread_t hare_id;
    uv_thread_t tortoise_id;
    uv_thread_create(&hare_id, hare, &tracklen);
    uv_thread_create(&tortoise_id, tortoise, &tracklen);

    uv_thread_join(&hare_id);
    uv_thread_join(&tortoise_id);
    return 0;
}


小贴士:
在Unix上uv_thread_t只是pthread_t的一个别名,但这是一个实现细节,避免依赖于它总是成立的。

第二个参数是函数作为线程的入口点,最后一个参数是一个void *参数可通过自定义参数到线程。函数hare现在将运行在一个单独的线程,由操作系统优先安排:


thread-create/main.c
void hare(void *arg) {
    int tracklen = *((int *) arg);
    while (tracklen) {
        tracklen--;
        sleep(1);
        fprintf(stderr, "Hare ran another step\n");
    }
    fprintf(stderr, "Hare done running!\n");
}


不像pthread_join()那样允许目标线程通过使用第二个参数返回一个值给调用线程,uv_thread_join() 不允许。返回值需要使用 Inter-thread communication。

同步原语

Mutexes互斥锁

互斥函数直接映射到pthread的等价物。

libuv 互斥函数

*处理编写到控制台。SIGWINCH可能并不总是及时交付;libuv只会在当光标被移动时检测尺寸变化。当一个可读的uv_tty_handle用在原始模式,调整控制台缓冲也将触发一个SIGWINCH信号

uv_mutex_init()和uv_mutex_trylock()函数成功时将返回0,错误时返回-1而不是错误代码。

如果libuv已经启用调试编译,uv_mutex_destroy(), uv_mutex_lock() 和 uv_mutex_unlock() 将被错误中止。同样如果错误是非EAGAIN的其它错误,uv_mutex_trylock()将中止。

一些平台支持递归互斥,但你不应该依靠他们。BSD互斥锁的实现,如果已锁定一个互斥量的再次的尝试锁定,将抛出一个错误。例如:

uv_mutex_lock(a_mutex);
uv_thread_create(thread_id, entry, (void *)a_mutex);
uv_mutex_lock(a_mutex);
// more things here


可以用来等待直到另一个线程初始化一些东西然后解锁互斥,但如果在调试模式下会导致程序崩溃,或第二个调用uv_mutex_lock()返回一个错误。

注意:
互斥锁在linux支持的属性为一个递归的互斥锁,但API不是通过libuv暴露。


锁定

读写锁是一个更细粒度的访问机制。两个读操作可以同时访问共享内存。一个写操作可能不获取锁,当它是由一个读操作持有。一位读或写可能不会获得一个锁当一个写操作持有它。读写锁在数据库经常用到。这是一个简单例子。
locks/main.c - simple rwlocks
#include <stdio.h>
#include <uv.h>

uv_barrier_t blocker;
uv_rwlock_t numlock;
int shared_num;

void reader(void *n)
{
    int num = *(int *)n;
    int i;
    for (i = 0; i < 20; i++) {
        uv_rwlock_rdlock(&numlock);
        printf("Reader %d: acquired lock\n", num);
        printf("Reader %d: shared num = %d\n", num, shared_num);
        uv_rwlock_rdunlock(&numlock);
        printf("Reader %d: released lock\n", num);
    }
    uv_barrier_wait(&blocker);
}

void writer(void *n)
{
    int num = *(int *)n;
    int i;
    for (i = 0; i < 20; i++) {
        uv_rwlock_wrlock(&numlock);
        printf("Writer %d: acquired lock\n", num);
        shared_num++;
        printf("Writer %d: incremented shared num = %d\n", num, shared_num);
        uv_rwlock_wrunlock(&numlock);
        printf("Writer %d: released lock\n", num);
    }
    uv_barrier_wait(&blocker);
}

int main()
{
    uv_barrier_init(&blocker, 4);

    shared_num = 0;
    uv_rwlock_init(&numlock);

    uv_thread_t threads[3];

    int thread_nums[] = {1, 2, 1};
    uv_thread_create(&threads[0], reader, &thread_nums[0]);
    uv_thread_create(&threads[1], reader, &thread_nums[1]);

    uv_thread_create(&threads[2], writer, &thread_nums[2]);

    uv_barrier_wait(&blocker);
    uv_barrier_destroy(&blocker);

    uv_rwlock_destroy(&numlock);
    return 0;
}


运行这个和观察读操作有时会重叠。对于多个写操作,调度程序通常会给他们更高的优先级,因此,如果您添加两个写操作,你就会看到,两个写操作倾向于先完成然后读操作有机会再次运行。

其它

libuv还支持信号量,条件变量和障碍的api非常类似于他们的pthread对应的内容。

对于条件变量,libuv也有一个等候超时,平台特定的怪癖[1]。

此外,libuv提供了一个方便的函数 uv_once()(不要uv_run_once()。多个线程可以尝试以一个给定的guard与一个函数指针调用 uv_once() ,只有第一个会赢,这个函数会被调用一次,只有一次:

/* Initialize guard */
static uv_once_t once_only = UV_ONCE_INIT;

int i = 0;

void increment() {
    i++;
}

void thread1() {
    /* ... work */
    uv_once(once_only, increment);
}

void thread2() {
    /* ... work */
    uv_once(once_only, increment);
}

int main() {
    /* ... spawn threads */
}


所有线程运行完之后,i==1。

libuv 工作队列

uv_queue_work()是一个很方便的功能,它允许一个应用程序在一个单独的线程运行一个任务,并有一个回调函数,当任务完成了触发。一个看似简单的功能,使uv_queue_work()诱人的是它允许潜在的任何第三方库用于与事件循环模式。当你使用事件循环,必须确保当执行I / O或是严重的CPU操作时在循环线程块没有定期运行的功能,因为这意味着循环减慢和在满负荷下事件没有得到处理。

但很多现有的代码以阻塞函数为特色(例如一个在hood下执行I/O的程序)来用于线程如果你想响应(典型的“一个客户端一个线程的”服务器模型),以及让他们运行于一个事件循环库,这些库通常包含滚动自己的运行的任务在一个单独的线程的系统。libuv只是为此提供了一个方便的抽象。

这是一个简单的例子受node.js激发的cancer。我们要计算斐波纳契数列,但运行它在一个单独的线程,以便阻止和CPU绑定的任务并不阻止事件循环执行其他活动。

queue-work/main.c - lazy fibonacci

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) {
    fprintf(stderr, "Done calculating %dth fibonacci\n", *(int *) req->data);
}


实际的任务函数很简单,没有什么表明它将运行在一个单独的线程。uv_work_t 的结构是线索。你可以通过使用void *数据字段传递任意数据和用它来和线程交流。但是如果你正在改变的内容通透式被两个可能运行线程使用到,要确保使用适当的锁。

触发者是uv_queue_work:

queue-work/main.c

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_work_t,一旦函数返回时,后续函数会被调用,同样以相同的结构。

编对于写包装器来封装库,一个常见的模式是使用一个baton来交换数据。

线程间通讯

有时你想要很多的线程来运行时实际地相互发送消息。例如你可能会在一个单独的线程运行一些长时间的任务(也许使用uv_queue_work),但想通知进展给主线程。这是一个简单的例子,有一个下载管理器通知用户下载的状态。

progress/main.c

uv_loop_t *loop;
uv_async_t async;

int main() {
    loop = uv_default_loop();

    uv_work_t req;
    int size = 10240;
    req.data = (void*) &size;

    uv_async_init(loop, &async, print_progress);
    uv_queue_work(loop, &req, fake_download, after);

    return uv_run(loop);
}


异步线程通信工作于循环,所以尽管任何线程可以做消息发送者,但只有libuv循环的线程与可以做接收器(或者说循环是接收器)。libuv将调用回调(print_progress)于异步观察者,每当它接收一条消息。

警告:

重要的是要意识到,消息发送是异步的,回调可能在uv_async_send在另一个线程被调用后立即被调用,或过一会调用。libuv也可以结合多个调用到uv_async_send,并调用回调只有一次。唯一的保证libuv做的是——回调函数在调用uv_async_send后至少调用一次。如果你没有将要调用uv_async_send,回调将不会被调用。如果你有两个或两个以上的调用,和libuv尚未有机会运行回调,但是它也许可以调用你的回调函数只有一次,对多个uv_async_send的调用。你的回调将从来不会为一个时间被调用两次。

progress/main.c

void fake_download(uv_work_t *req) {
    int size = *((int*) req->data);
    int downloaded = 0;
    double percentage;
    while (downloaded < size) {
        percentage = downloaded*100.0/size;
        async.data = (void*) &percentage;
        uv_async_send(&async);

        sleep(1);
        downloaded += (200+random())%1000; // can only download max 1000bytes/sec,
                                           // but at least a 200;
    }
}


在下载功能我们修改进度指示器和将消息放入队列,以便使用uv_async_send发送。记住:uv_async_send也非阻塞并将立即返回。

progress/main.c
void print_progress(uv_async_t *handle, int status /*UNUSED*/) {
    double percentage = *((double*) handle->data);
    fprintf(stderr, "Downloaded %.2f%%\n", percentage);
}


回调函数是一个标准的libuv模式,从观察者提取数据。

最后重要的是要记住清理观察者。

progress/main.c

void after(uv_work_t *req) {
    fprintf(stderr, "Download complete\n");
    uv_close((uv_handle_t*) &async, NULL);
}


在这个例子中,显示了滥用数据字段,bnoordhuis指出,使用数据字段不是线程安全的,而且 uv_async_send()实际上是只为了唤醒事件循环。使用互斥锁或rwlock确保访问以正确的顺序执行。

警告:
mutexes和rwlocks不工作在一个signal handler内部,而uv_async_send可以。

一个需要用到uv_async_send 的用例是当和库交互,为他们的功能要求线程关联。例如在 node.js,一个v8引擎实例中,上下文及其对象绑定到v8的实例起始的线程中。从另外一个线程和v8的数据结构互动会导致未定义的结果。现在考虑一些node.js模块结合了第三方库。它可能会是这样的:

1,在node中,第三方库设置有一个JavaScript回调函数来调用更多信息:

var lib = require('lib');
lib.on_progress(function() {
    console.log("Progress");
});

lib.do();

// do other stuff


2,lib.do应该是非阻塞而第三方部分是阻塞的,所以绑定使用uv_queue_work。

3,在一个单独的线程中已完成的工作想要调用进度回调,但不能直接调用v8 JavaScript交互。所以它使用uses uv_async_send。

4,异步回调,在v8线程的主循环线程中调用,然后与v8引擎交互来调用JavaScript回调。

[1] https://github.com/joyent/libuv/blob/master/include/uv.h#L1853
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    libuv--可调试(vs2008编译)

    2. **网络支持**: 提供TCP和UDP套接字的支持,可以处理TCP服务器和客户端,以及UDP广播和多播。 3. **文件系统操作**: 包括文件读写、目录遍历、文件同步和异步操作。 4. **进程和线程**: 支持子进程的创建和管理,...

    libuv-v1.9.0.tar.gz

    - **异步I/O**:libuv支持非阻塞的TCP、UDP套接字,文件系统操作,信号处理等,所有这些操作都可以在后台线程中异步完成,从而避免了主线程的阻塞。 - **事件循环**:libuv的核心是一个事件循环,负责监听并处理...

    libuv源码libuv-1.x

    在 libuv 中,可以创建和管理线程,实现多线程编程。同时,libuv 还提供了进程管理功能,包括创建子进程、向子进程发送信号、读取子进程的标准输出和错误流等。 5. **信号处理** libuv 支持注册信号处理器,可以...

    libuv-1.39.0.zip

    虽然libuv主要依赖于单线程事件循环,但它允许开发者在需要时利用多线程进行计算密集型任务,提高程序性能。1.39.0版本的线程池优化了任务调度和资源管理,使得多线程协作更为顺畅。 总的来说,libuv-1.39.0是一个...

    模拟摄像头libuv支持多线程并发

    本文将深入探讨“模拟摄像头libuv支持多线程并发”这一主题,结合“模拟IPC”这一标签,以及文件名称“IpcSimulate”,我们将分析如何利用libuv库在多线程环境下实现模拟摄像头的功能。 首先,让我们了解一下什么是...

    docs-libuv-org-en-v1.x.pdf

    12. **线程同步与互斥**:提供一系列线程同步原语,如锁、条件变量等,以保证多线程环境下的数据一致性。 #### 三、设计概览 libuv 是一个跨平台的支持库,最初为 Node.js 编写。它不仅仅是一个简单的不同 I/O ...

    libuv-master.tar.gz

    5. **进程和线程管理**:libuv提供了进程和线程的创建、信号处理、进程间通信(IPC)等功能,便于实现多进程和多线程编程。 6. **信号处理**:libuv可以注册回调来处理操作系统发送的信号,例如SIGINT(中断)或...

    libuv-v1.11.0.tar.gz

    此外,libuv还提供了进程和线程管理的功能,如子进程的创建和监控、线程池的使用。在新版本中,可能对线程池的调度策略进行了调整,以更好地利用多核处理器的优势,提高并行执行效率。 最后,libuv的异步信号处理和...

    VS2019编译好的libuv-v1.33.1库

    5. **进程和线程管理**:支持创建子进程,以及跨平台的线程池管理,简化多进程和多线程编程。 6. **事件循环**:Libuv 的核心是事件循环模型,它负责调度和分发各种异步操作,确保程序的高效运行。 在 VS2019 中...

    libuv多线程

    服务端代码进行封装,libuv以及pjsip等,支持扩展,便于开发.

    ocaml-libuv:OCaml 绑定到 libuv -- 跨平台异步 IO

    4. **线程池**:libuv 内建线程池,可以处理 CPU 密集型任务,避免了单一线程的性能瓶颈。 5. **事件循环**:libuv 的核心是事件循环,它负责调度和分发异步事件,保证程序的并发执行。 6. **跨平台兼容性**:libuv ...

    libuv 1.22.0动态链接库libuv.dll

    4. **进程和线程管理**:可以创建子进程,实现进程间通信(IPC),以及线程的同步和互斥。 5. **定时器和一次性任务**:支持定时触发回调,以及一次性任务的安排和执行。 总之,libuv 1.22.0动态链接库libuv.dll是...

    libuv-master.zip

    它适用于任何需要进行高效异步操作的场景,如实时Web服务、数据库连接、多线程任务调度等。 6. **学习与实践libuv** 要深入理解libuv,需要熟悉事件驱动编程模式,了解基本的网络协议和操作系统I/O模型。实践上,...

    libhv libev libuv libevent 2020-08-28 最新源代码

    此外,libevent还提供了多线程支持,使得开发者可以轻松地在多核环境中编写并发程序。 最后是**libuv**,它是Node.js项目的基础之一,由C语言编写,提供了跨平台的异步I/O抽象。libuv的核心是事件循环,它负责调度...

    cpp-libuv跨平台异步IO

    - **单线程与多线程**:libuv允许开发者在一个单独的线程中处理所有事件,同时也可以利用多线程来扩展处理能力,根据应用需求选择合适的策略。 2. **libuv的主要组件** - **事件循环(Event Loop)**:libuv的...

    cpp-libuv是一个多平台支持库专注于异步IO

    - **线程和进程管理**: 支持线程池、子进程创建与通信,便于进行多线程和分布式计算。 - **信号处理**: 可以注册信号处理器,接收并处理来自操作系统的信号,如SIGINT、SIGTERM等。 - **内存管理和日志记录**: ...

    read-libuv-code:libuv原始分析

    - **线程**:libuv不直接提供线程API,但通过工作队列间接支持线程化执行。 6. **信号处理** - libuv可以注册信号处理器,使得应用程序能响应特定的系统信号。 7. **日志和调试** - 提供日志记录功能,便于调试...

    libuv book 中文文档 An introduction to libuv

    4. **threads.html**: 讨论libuv的线程支持,包括线程池的使用和线程间的通信,这对于构建多线程应用至关重要。 5. **networking.html**: 这部分详细介绍了libuv的网络功能,如TCP/UDP套接字、HTTP服务器和客户端、...

    An Introduction to libuv.pdf

    - **多线程支持**:探讨libuv如何管理线程,包括线程间同步、工作队列(work queue)机制及线程间通信。 - **进程管理**:涵盖子进程的创建、参数设置、信号发送(signal sending)以及子进程输入输出管理等方面。 - **...

    libuv开发说明中文版

    在多线程方面,libuv提供线程池来处理一些耗时的任务,这样不会阻塞主线程,从而保证了事件循环的流畅运行。此外,libuv也支持创建和管理子进程,提供了创建新进程和执行外部程序的接口,这在执行需要隔离或并行处理...

Global site tag (gtag.js) - Google Analytics