技术分享: glib是如何来设计跨平台的线程库的?
(2) Linux 平台函数调用链
先来看一下 Linux 平台上的函数调用关系:
如果你的手边有源代码,请关注 g_thread_new() 这个函数中的 func 和 data 这2个参数。
func 是最开始用户层传入的线程执行函数,也就是用户创建这个线程,最终想执行的函数。data 是 func 函数所接收的函数参数。
如果直接面对 Linux 操作系统编程,在调用 POSIX 接口函数 pthread_create() 时,一般是直接传入用户想要执行的函数以及参数。
但是 glib 层并没有直接把用户层的函数直接交给 Linux 操作系统,而是自己提供了 2 个线程代理函数,在调用 pthread_create() 时,根据不同的情况,把这2个代理函数之一传递给操作系统:
第一个线程代理函数:g_thread_proxy();
第二个线程代理函数:linux_pthread_proxy();
至于传递哪一个代理函数,取决于宏定义 HAVE_SYS_SCHED_GETATTR 是否有效。
下面是 g_system_thread_new() 函数简化后的代码:
g_system_thread_new (proxy, stack_size, scheduler_settings,
name, func, data, error);
GThreadPosix *thread;
GRealThread *base_thread;
// 填充 base_thread 字段,重点关注下面2句
base_thread->thread.func = func;
base_thread->thread.data = data;
thread->scheduler_settings = scheduler_settings;
thread->proxy = proxy;
#if defined(HAVE_SYS_SCHED_GETATTR)
ret = pthread_create (&thread->system_thread, &attr, linux_pthread_proxy, thread);
#else
ret = pthread_create (&thread->system_thread, &attr, (void* (*)(void*))proxy, thread);
#endif
4. 线程的执行
我们就假设这个宏定义 HAVE_SYS_SCHED_GETATTR 被定义了、是有效的,Linux 系统中的 pthread_create() 接收到 linux_pthread_proxy() 函数。
当这个新建的线程被调度执行时,linux_pthread_proxy() 函数被调用执行:
简化后的 linux_pthread_proxy() 函数:
static void *
linux_pthread_proxy (void *data)
{
// data 就是 g_system_thread_new 中 GThreadPosix 类型指针,这是平台相关的。
GThreadPosix *thread = data;
if (thread->scheduler_settings)
{
// 设置线程属性
tid = (pid_t) syscall (SYS_gettid);
res = syscall (SYS_sched_setattr, tid, thread->scheduler_settings->attr, flags);
}
// 调用 glib 中的线程代理函数,其实就是 g_thread_proxy()
return thread->proxy (data);
}
这个函数关注 3 点:
data 参数: 就是 g_system_thread_new 函数中的GThreadPosix类型指针,这是平台相关的。
中间部分是设置线程属性;
最后的 return 语句,调用了 glib 中第一个线程代理函数 g_thread_proxy。
继续贴一下这个函数的简化后代码:
gpointer
g_thread_proxy (gpointer data)
{
// data 就是 g_system_thread_new 中 GThreadPosix 类型指针,这是平台相关的。
// 这里把它强转成平台无关的 GRealThread 类型。
GRealThread* thread = data;
if (thread->name)
{
// 设置线程属性:名称
g_system_thread_set_name (thread->name);
}
// 调用应用层的线程入口函数
thread->retval = thread->thread.func (thread->thread.data);
return NULL;
}
这个函数也只要关注 3 点:
data 参数: linux_pthread_proxy 函数传过来的是 GThreadPosix 类型指针,但是这里直接赋值给了 GRealThread 类型的指针,因为它们的内存模型是包含的关系;
中间部分是设置线程名称;
最后的 thread->thread.func (thread->thread.data) 语句,调用了用户最开始传入的函数并传递用户的 data 参数。
至此,用户层定义的线程函数 user_thread_func(data) 就得以执行了。
那么,如果 glib 层没有定义宏 HAVE_SYS_SCHED_GETATTR,那么 Linux 系统中 pthread_create() 接收到的就是 glib 中的第一个线程代理函数 g_thread_proxy。
线程执行的调用关系为:
5. Windows平台函数调用链
先来看一下 Windows 平台上创建线程时函数调用关系:
在 Windows 平台上,glib 的线程代理函数是 g_thread_win32_proxy()。
当这个新建的线程被调度执行时,函数调用关系是:
四、总结
实现这样的线程函数代理设计,关键是利用了 C 语言中的结构体类型中,把“父”结构体类型变量强制转换成“子”结构体类型变量来使用,因为它俩在内存模型中,刚开始部分的空间中,内容是完全一样的。
最后,我把文中的这些图合并起来,绘制成下面这 2 张图,完整的体现了 glib 中的线程设计思路:
Linux 平台:
Windows 平台:
图片新闻
最新活动更多
-
11月28日立即报名>>> 2024工程师系列—工业电子技术在线会议
-
11月29日立即预约>> 【上海线下】设计,易如反掌—Creo 11发布巡展
-
11月30日立即试用>> 【有奖试用】爱德克IDEC-九大王牌安全产品
-
即日-12.5立即观看>> 松下新能源中国布局:锂一次电池新品介绍
-
12月19日立即报名>> 【线下会议】OFweek 2024(第九届)物联网产业大会
-
即日-12.26火热报名中>> OFweek2024中国智造CIO在线峰会
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论