libevent 定制——libevent 定制多线程

news/2023/12/10 15:37:15

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);

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


https://dhexx.cn/news/show-5071625.html

相关文章

Rocketmq--消息发送和接收演示

使用Java代码来演示消息的发送和接收 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.0.2</version> </dependency> 1 发送消息 消息发送步骤: 创建…

Java基础入门·对存储文件File的相关操作

前言 File类获取的方法 getName() | getPath() File getAbsoluteFile() | File getParentFile() long length() File类遍历方法 IO流对象的分类 1.按照操作的文件类型分类 2.按照数据的流向分类 IO流对象的分类归纳 OutputStream 字节输出流写入文件的步骤 追加写入 F…

Linux系统编程6(线程互斥,锁,同步,生产消费模型)

上篇文章介绍完线程的概念后&#xff0c;我们将在这篇文章中初步探讨线程编程以及线程应用中的问题&#xff0c;这篇文章将以抢票系统为例&#xff0c;贯穿整篇文章。笔者将介绍在多线程编程中会出现的问题&#xff0c;什么是同步&#xff1f;什么是互斥&#xff1f;为什么多线…

虚拟化技术:深入浅出

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

react-route的路由

React-Router是一个基于React的强大路由库&#xff0c;它可以帮助我们在React应用中实现页面之间的跳转和路由管理。本文将详细介绍React-Router的路由功能、常用功能模块、路由传参和路由嵌套&#xff0c;并提供相关代码和解释。 路由功能 React-Router通过管理URL和组件的映…

Vue3 ~

变动 实例 const app new Vue({}) Vue.use() Vue.mixin() Vue.component() Vue.directive()const app Vue.createApp({}) app.use() app.mixin() app.component() app.directive()createApp 代替 new Vue 允许多个根标签 createStore 代替 Vue.use(Vuex) createRouter 代替…

以数据为中心的安全市场快速增长

根据Adroit Market Research的数据&#xff0c;2021年全球以数据为中心的安全市场规模估计为27.6亿美元&#xff0c;预计到2030年将增长至393.48亿美元&#xff0c;2021年至2030年的复合年增长率为30.9%。 研究人员表示&#xff0c;以数据为中心的安全强调保护数据本身&#x…

DMNet复现(一)之数据准备篇:Density map guided object detection in aerial image

一、生成密度图 密度图标签生成 采用以下代码&#xff0c;生成训练集密度图gt&#xff1a; import cv2 import glob import h5py import scipy import pickle import numpy as np from PIL import Image from itertools import islice from tqdm import tqdm from matplotli…

【QT5-解决不同分辨率屏幕-进行匹配大小-适应屏幕大小-基础样例】

【QT5-解决不同分辨率屏幕-进行匹配大小-适应屏幕大小】 1、前言2、实验环境3-1、问题说明-屏幕视频3-2、解决方式-个人总结解决思路&#xff1a;我们在软件启动的时候&#xff0c;先获取屏幕大小&#xff0c;然后根据长宽&#xff0c;按照一定比例&#xff0c;重新设置大小。并…

如何下载安装 WampServer 并结合 cpolar 内网穿透,轻松实现对本地服务的公网访问

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…