libevent 定制——libevent 定制多线程

chatgpt/2023/9/24 2:32:55

libevent 定制多线程

文章目录

  • libevent 定制多线程
    • 开启多线程
    • 定制多线程
    • 调试锁的使用

编写多线程程序的时候,在多个线程中同时访问同样的数据并不总是安全的。
libevent 的结构体在多线程下通常有三种工作方式:

  • 某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。

  • 某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。

  • 某些结构体总是锁定的:如果 libevent 在支持锁的配置下运行,在多个线程中使用它们总是安全的。

开启多线程

目前默认编译生成的libevent是支持多线程的,这一点可以从他的cmake过程文件(build/CMakeCache.txt)中看出:

EVENT__DISABLE_THREAD_SUPPORT:BOOL=OFF

之后这个宏会在libevent-2.1.12-stable/include/event2/thread.h这个提供给用户的头文件中用到:

#if !defined(EVENT__DISABLE_THREAD_SUPPORT) || defined(EVENT_IN_DOXYGEN_)#define EVTHREAD_LOCK_API_VERSION 1#define EVTHREAD_LOCKTYPE_RECURSIVE 1#define EVTHREAD_LOCKTYPE_READWRITE 2struct evthread_lock_callbacks {int lock_api_version;unsigned supported_locktypes;void *(*alloc)(unsigned locktype);void (*free)(void *lock, unsigned locktype);int (*lock)(unsigned mode, void *lock);int (*unlock)(unsigned mode, void *lock);
};EVENT2_EXPORT_SYMBOL
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);#define EVTHREAD_CONDITION_API_VERSION 1struct timeval;struct evthread_condition_callbacks {int condition_api_version;void *(*alloc_condition)(unsigned condtype);void (*free_condition)(void *cond);int (*signal_condition)(void *cond, int broadcast);int (*wait_condition)(void *cond, void *lock,const struct timeval *timeout);
};EVENT2_EXPORT_SYMBOL
int evthread_set_condition_callbacks(const struct evthread_condition_callbacks *);EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(unsigned long (*id_fn)(void));#if (defined(_WIN32) && !defined(EVENT__DISABLE_THREAD_SUPPORT)) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Windows builtin locking and thread IDfunctions.  Unavailable if Libevent is not built for Windows.@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_windows_threads(void);
/**Defined if Libevent was built with support for evthread_use_windows_threads()
*/
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED 1
#endif#if defined(EVENT__HAVE_PTHREADS) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Pthreads locking and thread ID functions.Unavailable if Libevent is not build for use with pthreads.  Requireslibraries to link against Libevent_pthreads as well as Libevent.@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_pthreads(void);
/** Defined if Libevent was built with support for evthread_use_pthreads() */
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1#endif/** Enable debugging wrappers around the current lock callbacks.  If Libevent* makes one of several common locking errors, exit with an assertion failure.** If you're going to call this function, you must do so before any locks are* allocated.**/
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debugging(void);/* Old (misspelled) version: This is deprecated; use* evthread_enable_log_debugging instead. */
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debuging(void);#endif /* EVENT__DISABLE_THREAD_SUPPORT */

从具体的代码可以看出,EVENT__DISABLE_THREAD_SUPPORT宏直接关系到libevent是否支持多线程,以及用户能够定制自己的多线程相关函数。

同时在thread.h文件中,还可以看到其对不同系统线程的支持(windows线程和pthread线程)。

  • 使用windows的线程
#if (defined(_WIN32) && !defined(EVENT__DISABLE_THREAD_SUPPORT)) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Windows builtin locking and thread IDfunctions.  Unavailable if Libevent is not built for Windows.@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_windows_threads(void);
/**Defined if Libevent was built with support for evthread_use_windows_threads()
*/
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED 1
#endif
  • 使用pthread线程
#if defined(EVENT__HAVE_PTHREADS) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Pthreads locking and thread ID functions.Unavailable if Libevent is not build for use with pthreads.  Requireslibraries to link against Libevent_pthreads as well as Libevent.@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_pthreads(void);
/** Defined if Libevent was built with support for evthread_use_pthreads() */
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1#endif

只有当你调用了evthread_use_windows_threads()或者evthread_use_pthreads()或者调用evthread_set_lock_callbacks函数定制自己的多线程、锁、条件变量才会开启多线程功能。其实,前面的那两个函数其内部实现也是定制,在函数的内部,libevent封装的一套Win32线程、pthreads线程。然后调用evthread_set_lock_callbacks函数,进行定制。以evthread_use_pthreads函数为例(libevent-2.1.12-stable/evthread_pthread.c),如果想了解windows可以查看evthread_use_windows_threads函数(libevent-2.1.12-stable/evthread_win32.c):

int
evthread_use_pthreads(void)
{struct evthread_lock_callbacks cbs = {EVTHREAD_LOCK_API_VERSION,EVTHREAD_LOCKTYPE_RECURSIVE,evthread_posix_lock_alloc,evthread_posix_lock_free,evthread_posix_lock,evthread_posix_unlock};struct evthread_condition_callbacks cond_cbs = {EVTHREAD_CONDITION_API_VERSION,evthread_posix_cond_alloc,evthread_posix_cond_free,evthread_posix_cond_signal,evthread_posix_cond_wait};/* Set ourselves up to get recursive locks. */if (pthread_mutexattr_init(&attr_recursive))return -1;if (pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE))return -1;evthread_set_lock_callbacks(&cbs);evthread_set_condition_callbacks(&cond_cbs);evthread_set_id_callback(evthread_posix_get_id);return 0;
}

从上面的代码可以看出,定制线程其实就定义了三类相关操作:

  • 锁相关操作,evthread_set_lock_callbacks
    • 锁定
    • 解锁
    • 分配锁
    • 析构锁
  • 条件变量相关操作,evthread_set_condition_callbacks
    • 条件变量
    • 创建条件变量
    • 析构条件变量
    • 等待条件变量
    • 触发/广播条件变量
  • 线程ID相关操作,evthread_set_id_callback

下面针对这三种操作详细说明

  • evthread_lock_callbacks结构体:
/** This structure describes the interface a threading library uses for* locking.   It's used to tell evthread_set_lock_callbacks() how to use* locking on this platform.*/
struct evthread_lock_callbacks {/** The current version of the locking API.  Set this to* EVTHREAD_LOCK_API_VERSION */int lock_api_version;/** Which kinds of locks does this version of the locking API* support?  A bitfield of EVTHREAD_LOCKTYPE_RECURSIVE and* EVTHREAD_LOCKTYPE_READWRITE.** (Note that RECURSIVE locks are currently mandatory, and* READWRITE locks are not currently used.)**/unsigned supported_locktypes;/** Function to allocate and initialize new lock of type 'locktype'.* Returns NULL on failure. */void *(*alloc)(unsigned locktype);/** Funtion to release all storage held in 'lock', which was created* with type 'locktype'. */void (*free)(void *lock, unsigned locktype);/** Acquire an already-allocated lock at 'lock' with mode 'mode'.* Returns 0 on success, and nonzero on failure. */int (*lock)(unsigned mode, void *lock);/** Release a lock at 'lock' using mode 'mode'.  Returns 0 on success,* and nonzero on failure. */int (*unlock)(unsigned mode, void *lock);
};

evthread_lock_callbacks 结构体描述的锁回调函数及其能力。

  • 对于上述版本,lock_api_version 字段必须设置为 EVTHREAD_LOCK_API_VERSION
  • 必须设置supported_locktypes 字段为 EVTHREAD_LOCKTYPE_*常量(EVTHREAD_LOCKTYPE_RECURSIVEEVTHREAD_LOCKTYPE_READWRITE)的组合以描述支持的锁类型(在2.0.4-alpha版本中 , EVTHREAD_LOCK_RECURSIVE是必须的,EVTHREAD_LOCK_READWRITE 则没有使用)。
  • alloc函数必须返回指定类型的新锁;
  • free函数必须释放指定类型锁持有的所有资源;
  • lock函数必须试图以指定模式请求锁定,如果成功则返回0,失败则返回非零;
  • unlock 函数必须试图解锁,成功则返回0,否则返回非零。

可识别的锁类型有:

  • 0:通常的,不必递归的锁。

  • EVTHREAD_LOCKTYPE_RECURSIVE:不会阻塞已经持有它的线程的锁。一旦持有它的线程进行原来锁定次数的解锁,其他线程立刻就可以请求它了。

  • EVTHREAD_LOCKTYPE_READWRITE:可以让多个线程同时因为读而持有它,但是任何时刻只有一个线程因为写而持有它。写操作排斥所有读操作。

可识别的锁模式有:

  • EVTHREAD_READ:仅用于读写锁:为读操作请求或者释放锁

  • EVTHREAD_WRITE:仅用于读写锁:为写操作请求或者释放锁

  • EVTHREAD_TRY:仅用于锁定:仅在可以立刻锁定的时候才请求锁定

  • evthread_condition_callbacks结构体:

/** This structure describes the interface a threading library uses for* condition variables.  It's used to tell evthread_set_condition_callbacks* how to use locking on this platform.*/
struct evthread_condition_callbacks {/** The current version of the conditions API.  Set this to* EVTHREAD_CONDITION_API_VERSION */int condition_api_version;/** Function to allocate and initialize a new condition variable.* Returns the condition variable on success, and NULL on failure.* The 'condtype' argument will be 0 with this API version.*/void *(*alloc_condition)(unsigned condtype);/** Function to free a condition variable. */void (*free_condition)(void *cond);/** Function to signal a condition variable.  If 'broadcast' is 1, all* threads waiting on 'cond' should be woken; otherwise, only on one* thread is worken.  Should return 0 on success, -1 on failure.* This function will only be called while holding the associated* lock for the condition.*/int (*signal_condition)(void *cond, int broadcast);/** Function to wait for a condition variable.  The lock 'lock'* will be held when this function is called; should be released* while waiting for the condition to be come signalled, and* should be held again when this function returns.* If timeout is provided, it is interval of seconds to wait for* the event to become signalled; if it is NULL, the function* should wait indefinitely.** The function should return -1 on error; 0 if the condition* was signalled, or 1 on a timeout. */int (*wait_condition)(void *cond, void *lock,const struct timeval *timeout);
};

evthread_condition_callbacks 结构体描述了与条件变量相关的回调函数。

  • 对于上述版本,condition_api_version字段必须设置为 EVTHREAD_CONDITION_API_VERSION
  • alloc_condition 函数必须返回到新条件变量的指针。它接受0作为其参数。
  • free_condition 函数必须释放条件变量持有的存储器和资源.
  • wait_condition 函数要求三个参数:一个由alloc_condition 分配的条件变量,一个由你提供的 evthread_lock_callbacks.alloc 函数分配的锁,以及一个可选的超时值。调用本函数时,必须已经持有参数指定的锁;本函数应该释放指定的锁,等待条件变量成为授信状态,或者直到指定的超时时间已经流逝(可选 )。wait_condition 应该在错误时返回-1,条件变量授信时返回0,超时时返回1。返回之前,函数应该确定其再次持有锁。
  • 最后,signal_condition 函数应该唤醒等待该条件变量的某个线程(broadcast 参数为 false 时),或者唤醒等待条件变量的所有线程(broadcast 参数为 true时)。只有在持有与条件变量相关的锁的时候,才能够进行这些操作。

关于条件变量的更多信息,请查看 pthreads 的 pthread_cond_*函数文档,或者 Windows的 CONDITION_VARIABLE(Windows Vista 新引入的)函数文档。

  • id_fn
/**Sets the function for determining the thread id.@param base the event base for which to set the id function@param id_fn the identify function Libevent should invoke todetermine the identity of a thread.
*/
EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(unsigned long (*id_fn)(void));

id_fn 参数必须是一个函数,它返回一个无符号长整数,标识调用此函数的线程。对于相同线程,这个函数应该总是返回同样的值;而对于同时调用该函数的不同线程,必须返回不同的值。

如果用户为libevent开启了多线程,那么libevent里面的函数就会变成线程安全的。此时主线程在使用event_base_dispatch,别的线程是可以线程安全地使用event_add把一个event添加到主线程的event_base中。

定制多线程

libevent定制多线程其实就是定制多线程中会使用到的锁、条件变量、id,这点可以从libevent-2.1.12-stable/include/event2/thread.h文件中知道:

  • evthread_set_lock_callbacks
/** Sets a group of functions that Libevent should use for locking.* For full information on the required callback API, see the* documentation for the individual members of evthread_lock_callbacks.** Note that if you're using Windows or the Pthreads threading library, you* probably shouldn't call this function; instead, use* evthread_use_windows_threads() or evthread_use_posix_threads() if you can.*/
EVENT2_EXPORT_SYMBOL
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);
  • evthread_set_condition_callbacks
/** Sets a group of functions that Libevent should use for condition variables.* For full information on the required callback API, see the* documentation for the individual members of evthread_condition_callbacks.** Note that if you're using Windows or the Pthreads threading library, you* probably shouldn't call this function; instead, use* evthread_use_windows_threads() or evthread_use_pthreads() if you can.*/
EVENT2_EXPORT_SYMBOL
int evthread_set_condition_callbacks(const struct evthread_condition_callbacks *);
  • evthread_set_id_callback
/**Sets the function for determining the thread id.@param base the event base for which to set the id function@param id_fn the identify function Libevent should invoke todetermine the identity of a thread.
*/
EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(unsigned long (*id_fn)(void));

一旦用户调用evthread_use_windows_threads()或者evthread_use_pthreads()函数,那么用户就为libevent定制了自己的线程锁操作。libevent的其他代码中,如果需要用到锁,就会去调用这些线程锁操作。在实现上,当调用evthread_use_windows_threads()或者evthread_use_pthreads()函数时,两个函数的内部都会调用evthread_set_lock_callbacks函数。而这个设置函数会把前面两个evthread_use_xxx函数中定义的cbs变量值复制到一个evthread_lock_callbacks类型的_evthread_lock_fns全局变量保存起来。以后,libevent需要用到多线程锁操作,直接访问这个_evthread_lock_fn变量即可。对于条件变量,也是用这样方式实现的。

为获取锁,在调用分配需要在多个线程间共享的结构体的 libevent 函数之前,必须告知libevent 使用哪个锁函数。如果使用 pthreads 库,或者使用 Windows 本地线程代码,那么已经有设置libevent 使用正确的 pthreads 或者 Windows 函数的预定义函数。

调试锁的使用

为帮助调试锁的使用,libevent 有一个可选的“锁调试”特征。这个特征包装了锁调用,以便捕获典型的锁错误,包括:

  • 解锁并没有持有的锁

  • 重新锁定一个非递归锁
    如果发生这些错误中的某一个,libevent 将给出断言失败并且退出。

/** Enable debugging wrappers around the current lock callbacks.  If Libevent* makes one of several common locking errors, exit with an assertion failure.** If you're going to call this function, you must do so before any locks are* allocated.**/
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debugging(void);

必须在创建或者使用任何锁之前调用这个函数。为安全起见,请在设置完线程函数后立即调用这个函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-5413947.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

CSS动效合集之实现气泡发散动画

前言 👏CSS动效合集之实现气泡发散动画,速速来Get吧~ 🥇文末分享源代码。记得点赞关注收藏! 1.实现效果 2.实现步骤 定义一个数组bubbles,用来存储气泡列表的基本新,w表示宽高,x表示绝对定位…

使用 Docker 安装 Elasticsearch (本地环境 M1 Mac)

Elasticsearchkibana下载安装 docker pull elasticsearch:7.16.2docker run --name es -d -e ES_JAVA_OPTS“-Xms512m -Xmx512m” -e “discovery.typesingle-node” -p 9200:9200 -p 9300:9300 elasticsearch:7.16.2docker pull kibana:7.16.2docker run --name kibana -e EL…

AOSP Android 系统源码编译出的framework.jar和android.jar之间的区别

简介 AOSP(Android Open Source Project)编译出的 android.jar 和 framework.jar 都是 Android 平台开发中的重要组件,但它们有不同的作用和用途: android.jar: 用途:android.jar 包含了 Android API 的定…

【Linux】【驱动】平台总线模式platform模型的编写,操作LED灯

【Linux】【驱动】平台总线模式platform模型的编写,操作LED灯 续devicedriverapp操作指令 续 平台总线模式,将设备和驱动进行了区分,据说可以优化代码的可移植性 device:实现对设备寄存器的申明 dirver: 则从总线中取数据&#x…

【牛客刷题】最小k个数

最小k个数 https://www.nowcoder.com/share/jump/9321389651694956999635 采用优先级队列构造小顶堆&#xff0c;时间复杂度为O(n * log n)&#xff0c;空间复杂度O(n)&#xff1a; priority_queue<int, vector, greater> qp; vector<int> GetLeastNumbers_Solut…

redis桌面连接工具Another Redis Desktop Manager使用介绍

Another Redis Desktop Manager是一种类似于navicat的数据库连接工具&#xff0c;专门用来连接redis&#xff0c;使用起来非常简单方便&#xff0c;在这里推荐给大家。 没有用过这个软件的&#xff0c;首先通过下面的网盘链接下载Another Redis Desktop Manager 百度网盘redi…

明星翻包视频的崛起:探究背后的驱动力

近年来&#xff0c;社交媒体上涌现出越来越多的明星翻包视频&#xff0c;成为广大粉丝和观众们的追捧对象。这个趋势的背后是什么原因&#xff1f;为什么现今的明星都热衷于分享自己的私人物品和生活片段&#xff1f; 在明星翻包视频的制作和推广过程中&#xff0c;媒介易作为专…

【pytest】 pytest拓展功能 PermissionError问题

目录 1. pytest-html 1.1 PermissionError: [Errno 13] Permission denied: D:\\software\\python3\\anconda3\\Lib\\site-packages\\pytest_html\\__pycache__\\tmp_ttoasov 1.2错误原因 2. 失败用例重试 3. 用例并行执行 pytest-parallel 1. pytest-html 管理员打开 A…
推荐文章