zoukankan      html  css  js  c++  java
  • flutter: 线程通信与消息循环(C++)

    环境: flutter sdk v1.5.4-hotfix.1@stable

    对应 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f

    这里关注的是flutter在C++层的线程表示, 没有涉及dart层的线程

    线程创建

    flutter底层(C++)的线程(fml::Thread)是和消息循环紧密关联的,即每一个fml::Thead实例都创建了一个消息循环实例,因此如果要创建一个裸线程是不应该用fml::Thread的。fml::Thread内部即是用C++11的std::thread来持有一个线程对象,参看fml::Thread构造函数(thread.cc:25)。

    线程运行体做了2件事

    1. 创建消息循环实例并关联线程fml::Thread对象
    2. 获取消息循环的TaskRunner对象实例并赋值给线程fml::Thread,即线程也持有一个TaskRunner实例

    这个TaskRunner是个干啥的,还得看的fml::MessageLoop实现
    fml::Thread的实现非常简单,关键还是看它关联的fml::MessageLoop

    线程存储

    消息循环fml::MessageLoop首先用了线程存储来保存一个回调,这个回调的作用是显式释放一个fml::MessageLoop内存对象,所以先搞清flutter底层是如何进行线程存储的。

    线程存储对象即作用域与线程生命周期一致的存储对象,fml::ThreadLocal即为线程存储类,它要保存的值是一个类型为intptr_t的对象;
    fml::ThreadLocal在不同平台用了不同的实现方式

    1. 类linux平台
      用了pthread的库函数pthread_key_create来生成一个标识线程的key键,key对应的值是一个辅助类Box,它保存了intptr_t对象和传入的回调方法ThreadLocalDestroyCallbackThreadLocal使用前需要声明的关键字是static
      对象析构的顺序稍有点绕, 各对象析构调用序列如下:
    ThreadLocal::~ThreadLocal()
      ThreadLocal::Box::~Box()
      pthread_key_delete(_key)
        ThreadLocal::ThreadLocalDestroy
            ThreadLocal::Box::DestroyValue
              ThreadLocalDestroyCallback() => [](intptr_t value) {}
                MessageLoop::~MessageLoop()
            ThreadLocal::Box::~Box()
    

    这样看似乎thread_local.cc:27处的delete操作是多余的?
    2. windows平台
    ThreadLocal使用前直接用了C++11标准的关键字thread_local

    消息循环

    消息循环即异步处理模型,在没有消息时阻塞当前线程以节省CPU消耗,否则以轮询的方式空转很浪费CPU资源,消息循环在安卓平台上很常见,其实所有的消息循环都大同小异。

    关联线程

    明白了线程存储,那么在创建fml::Thread对象时调用的MessageLoop::EnsureInitializedForCurrentThread就很浅显了(名字虽然有点累赘),当前线程是否创建了消息循环对象,如果没有那么创建并保存。这样消息循环就与线程关联起来了, 通过什么关联的?tls_message_loop这个线程存储类对象。

    消息队列

    MessageLoopImpl ::delayed_tasks_就是实际的消息队列,它被delayed_tasks_mutex_这个互斥变量保证线程安全。看着有点累赘,其实就是用了一个优先级队列按执行时间点来插入,如果时间点相同就按FIFO的规则来插入。

    队列元素是一个内部类DelayedTask, 主要包含消息执行体task和执行时间点target_timeorder其实是用来排序的。

    循环实现

    MessageLoop对象构造函数创建了2个重要实例,消息循环实现体MessageLoopImplfml::TaskRunner, 而fml::TaskRunner内部又引用了MessageLoopImplMessageLoopImpl::Create()创建了不同平台对应的消息循环实现体,于是MessageLoopMessageLoopImpl之间的关系也非常清楚了: MessageLoopMessageLoopImpl的壳或者MessageLoopImplMessageLoop的代理,MessageLoopImpl是不对外暴露的、与平台相关的、真正实现消息读取与处理的对象。

    MessageLoopImpl::Run,Terminate,WakeUp是纯虚函数,由平台实现,譬如安卓平台的实现MessageLoopAndroid调用的是AndroidNDK方法ALooper_pollOnce, MessageLoopLinux调用是Linux阻塞函数epoll_wait

    这里涉及的类和方法有点绕,其实想达到目的很简单:读取并处理消息的操作是统一的,但线程唤醒或者阻塞的方式是允许平台差异的

    发送消息

    一个消息循环关联一个TaskRunner,而TaskRunner细看实现发现全都是MessageLoopImpl的方法,再联系之前在AndroidShellHolder构造函数里创建的TaskHost,就可以发现所谓的TaskRunner无非就是给指定消息循环发送消息,而一个消息循环是和一个线程(fml::Thread)关联的,因而也也就是给指定线程发送消息,没错,正是线程间通信!TaskRunner也正是声明成了线程安全对象(fml::RefCountedThreadSafe<TaskRunner>)

    这样其实一切都串联起来了: fml::TaskRunner正如android中的android.os.Handler, fml::closure正如android中的Runnable, fml::TaskRunner不断的将各种fml::closure对象添加到消息队列当中,并设定消息循环在指定的时间点唤醒并执行。

    线程结束

    fml::Thread析构函数调用了自身的Join方法, 这个操作初看有点别扭,后来才明白意图:主调线程需要同步的等待被调线程结束,名称不如Exit来的言简意赅。Join方法先异步发送了一个结束消息循环的请求(MessageLoop::GetCurrent().Terminate()),然后阻塞式等待结束。
    结合以上列出线程退出的调用序列:

    Thread::~Thread()
      Thread::Join()
        TaskRunner::PostTask()
    ...[异步]
    MessageLoop::Terminate()
      MessageLoopImpl::DoTerminate()
        MessageLoopImpl::Terminate() => MessageLoopAndroid::Terminate()
          ALooper_wake()
    
    ...[异步,函数开始返回] 
        MessageLoopImpl::Run() => MessageLoopAndroid::Run()
        MessageLoopImpl::RunExpiredTasksNow()
      MessageLoopImpl::DoRun()
    MessageLoop::Run()
    
    ...[异步]
    ThreadLocal::~ThreadLocal()
    [省略,同线程存储对象析构的调用序列]
    

    线程体系

    回看AndroidShellHolder的构造函数,其中涉及flutter::ThreadHost, fml::TaskRunner, flutter::TaskRunners,在创建Shell对象之前还创建了一系列线程:ui_thread, gpu_thread, io_thread,并对TaskRunner有一系列操作,有点杂乱但现在看其实就非常清晰了。

    当前执行AndroidShellHolder构造函数的线程被创建了一个消息循环(android_shell_holder.cc:81)并将消息循环的TaskRunner赋值给了platform_runner注意:并没有创建platform_thread对象)。其它的TaskRunner则分别是所创建的fml::Thread线程的TaskRunner对象。

    那么问题来了:当某个线程通过platform_runner发送一个异步请求时,会在什么时机执行?

  • 相关阅读:
    Codeforces A. Bear and Big Brother
    codeforces A. In Search of an Easy Problem
    c#判断两个对象和对象中的属性是否相同(以及记录对象中的哪些字段,和详细的改变情况)
    生成随机字符串
    SQL语句计算距离今天生日还差几天
    sqlServer 获取最新的一条数据
    c#所有部门及其下所部门生成树形图(递归算法获取或键值对方式获取)
    根据中文名,自动生成首字母的拼音码或拼音码(两种方法)
    char/varchar/nvarchar的区别
    c#中ofType的用法
  • 原文地址:https://www.cnblogs.com/lindeer/p/11133790.html
Copyright © 2011-2022 走看看