libuv线程
1、概述
libuv的线程API与Linux的pthread的API在使用方法和语义上很接近,因为要跨平台,所以libuv支持的线程API个数很有限。libuv中只有一个主线程,主线程上只有一个event loop。如下为创建线程的一个简单示例:
#include <stdio.h> #include <uv.h> void thread_fun(void* arg) { //线程方法 fprintf(stdout, "thread start\n"); int param = *((int*)arg); } int main() { int param = 100; uv_thread_t thread_id; uv_thread_create(&thread_id, thread_fun, ¶m); //创建线程并开始 uv_thread_join(&thread_id); //等待线程结束 return 0; }
2、线程同步
①、互斥量
UV_EXTERN int uv_mutex_init(uv_mutex_t* handle); //执行成功返回0,错误返回错误码 UV_EXTERN void uv_mutex_lock(uv_mutex_t* handle); //调试模式下出错会引发abort()中断,而不是返回错误码 UV_EXTERN int uv_mutex_trylock(uv_mutex_t* handle); //libuv的API中并未实现递归上锁 UV_EXTERN void uv_mutex_unlock(uv_mutex_t* handle); UV_EXTERN void uv_mutex_destroy(uv_mutex_t* handle);
②、读写锁
int uv_rwlock_init(uv_rwlock_t* rwlock); void uv_rwlock_rdlock(uv_rwlock_t* rwlock); //读 int uv_rwlock_tryrdlock(uv_rwlock_t* rwlock); void uv_rwlock_rdunlock(uv_rwlock_t* rwlock); void uv_rwlock_wrlock(uv_rwlock_t* rwlock); //写 int uv_rwlock_trywrlock(uv_rwlock_t* rwlock); void uv_rwlock_wrunlock(uv_rwlock_t* rwlock); void uv_rwlock_destroy(uv_rwlock_t* rwlock);
③、屏障
libuv中屏障相关方法分别对应linux中pthread_xxx()相关方法的功能,“屏障”就相当于是线程的栏杆。可以将多个线程挡在同一栏杆前,直到所有线程到齐,然后撤下栏杆同时放行。
int uv_barrier_init(uv_barrier_t* barrier, unsigned int count); //初始化屏障并指定要等待的线程个数 int uv_barrier_wait(uv_barrier_t* barrier); //在线程中调用该方法,告诉屏障我已经到达栏杆处了,然后屏障会检查是否所有线程都已经到达,否的话已经到达栏杆的线程会阻塞,是的话放行所有线程 void uv_barrier_destroy(uv_barrier_t* barrier); //释放屏障
④、其它
libuv同样支持信号量,条件变量,而且API的使用方法和pthread中的用法很类似,具体可以参考libuv文档。
libuv中还提供一个uv_once()方法,在多个线程中通过uv_once来调用指定方法的话,该方法只会被一个线程所调用,如下所示两个线程执行完毕后g_i的值是1:
uv_once_t once_only = UV_ONCE_INIT; int g_i = 0; void increment() { i++; } void thread1() { /* ... work */ uv_once(&once_only, increment); } void thread2() { /* ... work */ uv_once(&once_only, increment); }
3、TLS(线程局部存储)
TLS就是只能被线程内的各个方法访问的变量,该变量不能被其它线程访问。如下为libuv中TLS相关方法,具体可以参考libuv文档。
int uv_key_create(uv_key_t* key) //对应pthread中的pthread_key_create() void uv_key_delete(uv_key_t* key) void* uv_key_get(uv_key_t* key) void uv_key_set(uv_key_t* key, void* value)
4、任务队列
uv_queue_work()会使用线程池执行指定的任务(线程池默认大小为4,可以通过环境变量UV_THREADPOOL_SIZE来设置),而且当任务完成后会在主线程中运行指定的回调。uv_queue_work()的第二个参数需要一个uv_work_t(任务方法和完成通知回调方法的参数),可以通过uv_work_t::data域来传递上下文数据,如下所示将uv_work_t放到上下文数据结构中的话,可以一次malloc把uv_work_t和数据都申请了出来。
uv_cancel()可以取消工作队列中的任务,其参数为uv_work_t。只有还未开始的任务可以被取消,此时回调通知方法中的status参数值为UV_ECANCELED,如果任务已经开始执行或者执行完毕,uv_cancel()返回0。uv_cancel()同样可以用在uv_fs_t和uv_getaddrinfo_t请求上。
#include <stdio.h> #include <uv.h> struct SData { uv_work_t req; char* str; int n = 0; }; void fun(uv_work_t* req) { SData* pBaton = (SData*)req->data; } void after_fun(uv_work_t* req, int status) { if(status == UV_ECANCELED) { //任务被取消 } else { } SData* pBaton = (SData*)req->data; free(pBaton->str); free(pBaton); } int main() { uv_loop_t* loop = uv_default_loop(); SData* pBaton = (SData*)malloc(sizeof(SData)); pBaton->req.data = (void*)pBaton; pBaton->str = _strdup("str"); uv_queue_work(loop, &pBaton->req, fun, after_fun); return uv_run(loop, UV_RUN_DEFAULT); }
5、异步调用
使用uv_async_init()和uv_async_send()可以异步的执行一个方法,该方法会在event-loop主线程中执行。uv_async_send()相当于是向主线程发了一个消息,主线程收到消息后会执行uv_async_init()设置的方法。有可能多次调用uv_async_send后只运行了一次回调函数:比如你调用了两次uv_async_send(), 而 libuv很忙,暂时还没有机会运行回调函数。
#include <stdio.h> #include <uv.h> uv_async_t async; //异步对象 uv_work_t req; void thread_fun(uv_work_t* req) { async.data = (void*)_strdup("data"); uv_async_send(&async); //执行异步对象 } void after_thread_fun(uv_work_t* req, int status) { uv_close((uv_handle_t*)&async, NULL); //释放异步对象,任务完成的回调after_thread_fun()会在异步方法async_fun()之后执行 } void async_fun(uv_async_t* handle) { char* pData = (char*)handle->data; free(pData); } int main() { uv_loop_t* loop = uv_default_loop(); uv_async_init(loop, &async, async_fun); //初始化异步对象 uv_queue_work(loop, &req, thread_fun, after_thread_fun); return uv_run(loop, UV_RUN_DEFAULT); }