通用跨平台库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 平台:


声明: 本文由入驻OFweek维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存