前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Chromium】Base库的RunLoop

【Chromium】Base库的RunLoop

原创
作者头像
lealc
修改2024-03-29 10:48:35
1410
修改2024-03-29 10:48:35
举报
文章被收录于专栏:Chromium学习Chromium学习

源码

先附上可用于学习的开源代码:Base库

喜欢可以帮忙Star一下

前言

编译:参考Base库即可

环境:Visual Studio 2022 - 17.8.3 + v143 + 10.0.22621.0 + C++17

RunLoop

Base 库中的 RunLoop 是一个事件循环机制,用于处理异步事件和任务调度。它提供了一种方便的方式来管理和调度事件的处理,特别适用于多线程和异步编程环境。

RunLoop 的主要特点和功能包括:

  1. 事件循环:RunLoop 提供了一个循环,可以不断地处理事件和任务。它会等待事件的到来,并根据事件的类型和优先级执行相应的处理函数。
  2. 任务调度:RunLoop 允许将任务(也称为延迟任务)提交到事件循环中,以在指定的时间点或条件下执行。这可以用于实现定时任务、延迟任务和周期性任务等。
  3. 事件分发:RunLoop 可以将事件分发给注册的事件处理函数,以便进行相应的处理。这包括处理用户输入、网络事件、定时器事件等。
  4. 线程安全:RunLoop 是线程安全的,可以在多个线程上使用。它提供了一些线程间同步的机制,以确保事件和任务的正确执行。
  5. 可扩展性:RunLoop 具有良好的可扩展性,可以支持大量的事件和任务,并能够高效地处理它们。

Chromium 使用 RunLoop 来管理和调度各种异步操作,如网络请求、定时器、UI 事件等。它是 Chromium 内部的核心机制之一,为 Chromium 浏览器的高性能和稳定性做出了重要贡献。

为何要有这个?

创建基本的事件循环,可以让一个线程从干一件事就退出,变为可以循环干很多件事。从这个角度出发,结合base::SimpleThread可以使之成为base::Thread,相当于增强了base::SimpleThread。

源码赏析

RunLoop与SimpleThread相同,将真正的Run函数代理给其中的Delegate类,这种代理设计模式在Chromium大量存在

RunLoop::Delegate

RunLoop::Delegate 是一个通用接口,允许 RunLoop 与该线程的消息循环的底层实现分离。

它持有在关联线程上由 RunLoop 使用的私有状态。

在使用 RunLoop 实例和 RunLoop 静态方法之前,必须通过 RunLoop::RegisterDelegateForCurrentThread() 在给定线程上注册一个且仅一个 RunLoop::Delegate。完整源码如下:

代码语言:C++
复制
class BASE_EXPORT Delegate {
public:
    Delegate();
    virtual ~Delegate();

    virtual void Run(bool application_tasks_allowed, TimeDelta timeout) = 0;
    virtual void Quit() = 0;

    // 当 RunLoop 是类型 kNestableTasksAllowed 时,在 RunLoop 进入此 Delegate 的嵌套 Run() 调用之前调用。
    // Delegate 应确保即将发生的 Run() 调用将处理排在其前面的应用程序任务,而无需进一步探测。
    // 例如,在某些平台上,如 Mac,消息泵需要显式请求在嵌套时处理应用程序任务,否则它们只会等待系统消息。
    virtual void EnsureWorkScheduled() = 0;

protected:
    // 返回此 Delegate 的 |should_quit_when_idle_callback_| 的结果。
    // 仅由 Delegate 本身调用,因此设置为 "protected"。
    bool ShouldQuitWhenIdle();

private:
    // 虽然状态由 Delegate 子类持有,但只有 RunLoop 可以使用它。
    friend class RunLoop;

    // 使用基于向量的堆栈比默认的双端队列堆栈更节省内存,因为预计活动的 RunLoop 堆栈不会超过几个条目。
    using RunLoopStack = stack<RunLoop*, std::vector<RunLoop*>>;

    RunLoopStack active_run_loops_;
    ObserverList<RunLoop::NestingObserver>::Unchecked nesting_observers_;

#if DCHECK_IS_ON()
    bool allow_running_for_testing_ = true;
#endif

    // 一旦通过 RegisterDelegateForCurrentThread() 将此 Delegate 绑定到线程,此值将为 true。
    bool bound_ = false;

    // Thread-affine per its use of TLS.
    THREAD_CHECKER(bound_thread_checker_);

    DISALLOW_COPY_AND_ASSIGN(Delegate);
};

Run和Quit

这两个函数一般用于RunLoop来调用,通知其注册的Delegate来运行/终止。实现应该从 Run() 调用开始同步运行,直到收到匹配的 Quit() 调用或超时的 |timeout|。收到 Quit() 调用或超时后,应尽快从 Run() 调用返回,而不执行剩余的任务/消息。

Run() 调用可以嵌套,在这种情况下,每个 Quit() 调用应导致最顶层的活动 Run() 调用返回。Run() 返回的另一个触发条件是 Delegate 在空闲时应该先探测 |shouldquit_when_idle_callback|,然后再休眠。

如果此栈上是首次 Run() 调用,或者是从 Type::kNestableTasksAllowed 的嵌套 RunLoop 调用的话,|application_tasks_allowed| 为 true(否则此 Run() 级别应仅处理系统任务)。

注意Run和Quit都是纯虚函数,意味着不能直接使用Deltegate类,必须继承使用

RunLoop::Type

代码语言:C++
复制
 enum class Type {
    kDefault,
    kNestableTasksAllowed,
  };

RunLoop 的类型:顶级(非嵌套)的 RunLoop(kDefault)将处理分配给其 Delegate 的系统和应用程序任务。然而,当嵌套时,RunLoop(kDefault) 仅处理系统任务,而 RunLoop(kNestableTasksAllowed) 将继续处理应用程序任务。

这在递归 RunLoop 的情况下非常重要。在使用常见控件或打印机功能时,可能会出现一些不需要的运行循环。默认情况下,禁用递归任务处理。

一般来说,应尽量避免使用可嵌套的 RunLoop。它们很危险,很难正确使用,请谨慎使用。

一个具体的例子是:

  • 线程正在运行一个 RunLoop。
  • 它收到任务 #1 并执行它。
  • 任务 #1 隐式启动了一个 RunLoop,例如在单元测试中的 MessageBox。这也可以是 StartDoc 或 GetSaveFileName。
  • 在第二个 RunLoop 中或在这个第二个 RunLoop 中,线程收到任务 #2。
  • 对于 kNestableTasksAllowed RunLoop,任务 #2 将立即运行。否则,它将在主 RunLoop 中任务 #1 完成后立即执行。

构造和析构

默认构造type = Default,表明非可嵌套,尽量避免创建嵌套循环

代码语言:C++
复制
RunLoop::RunLoop(Type type)
    : delegate_(GetTlsDelegate().Get()),
      type_(type),
      origin_task_runner_(ThreadTaskRunnerHandle::Get()) {
  DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior "
                       "to using RunLoop.";
  DCHECK(origin_task_runner_);
}

RunLoop::~RunLoop() {
  // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
  // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

这里可以看到,RunLoop执行任务的能力是由ThreadTaskRunnerHandle::Get()赋予的

运行

源码注释如下:

代码语言:C++
复制
// 运行当前的 RunLoop::Delegate。这会阻塞直到调用 Quit。在调用 Run 之前,请确保获取 QuitClosure 以便异步停止 RunLoop::Delegate。
void Run();

// 运行当前的 RunLoop::Delegate。这会阻塞直到经过 |timeout| 或调用 Quit。支持嵌套多个具有和不具有超时的 runloop。如果内部循环的超时时间比外部循环长,当内部循环退出时,外部循环将立即退出。
void RunWithTimeout(TimeDelta timeout);

// 运行当前的 RunLoop::Delegate,直到其队列中不再有任何任务或消息(即进入空闲状态)。
// 警告1:这可能运行时间很长(可能会超时),甚至永远不会返回!当存在重复任务(例如动画网页)时,请勿使用此方法。
// 警告2:这可能会过早返回!例如,如果用于运行直到发生传入事件,但该事件依赖于不同队列中的任务(例如另一个 TaskRunner 或系统事件)。
// 根据上述警告,这往往会导致测试不稳定;在可能的情况下,更倾向于使用 
QuitClosure()+Run()。
void RunUntilIdle();

// 返回当前是否正在运行
bool running() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return running_;
}

终止

代码语言:C++
复制
// Quit() 立即终止先前对 Run() 的调用。QuitWhenIdle() 在队列中没有任务或消息时终止先前对 Run() 的调用。
//
// 这些方法是线程安全的,但请注意,当从另一个线程调用 Quit() 时,它是异步的(会很快退出,但已经排队的任务会先执行)。
//
// 可能会有其他嵌套的 RunLoop 服务于相同的任务队列。终止一个 RunLoop 对其他 RunLoop 没有影响。Quit() 和 QuitWhenIdle() 可以在 Run() 之前、期间或之后调用。如果在调用 Run() 之前调用 Quit() 或 QuitWhenIdle(),则调用 Run() 将立即返回。在 RunLoop 已经完成运行后调用 Quit() 或 QuitWhenIdle() 没有效果。
//
// 警告:您绝不能假设调用 Quit() 或 QuitWhenIdle() 将终止目标消息循环。如果嵌套的 RunLoop 继续运行,目标可能永远不会终止。在这种情况下,很容易发生活锁(永远运行)。
void Quit();
void QuitWhenIdle();

// 返回一个 RepeatingClosure,安全地调用 Quit() 或 QuitWhenIdle()(如果 RunLoop 实例不存在,则没有效果)。
//
// 这些方法必须从创建 RunLoop 的线程调用。
//
// 返回的闭包可以安全地:
//   * 传递到其他线程。
//   * 从其他线程运行 Run(),尽管这将异步地终止 RunLoop。
//   * 在 RunLoop 停止或被销毁后运行,此时它们不起作用。
//   * 在 RunLoop::Run() 之前运行,此时 RunLoop::Run() 将立即返回。
// 
// 例子:
// RunLoop run_loop;
// DoFooAsyncAndNotify(run_loop.QuitClosure());
// run_loop.Run();

// 请注意,Quit() 本身是线程安全的,如果您可以从另一个线程访问 RunLoop 引用(例如从捕获的 lambda 函数或测试观察者中),可以直接调用它。
RepeatingClosure QuitClosure();
RepeatingClosure QuitWhenIdleClosure();

静态方法

代码语言:C++
复制
// 如果当前线程上有一个活动的 RunLoop,则返回 true。
// 在调用 RegisterDelegateForCurrentThread() 之前调用是安全的。
static bool IsRunningOnCurrentThread();

// 如果当前线程上有一个活动的 RunLoop,并且它嵌套在另一个活动的 RunLoop 中,则返回 true。
// 在调用 RegisterDelegateForCurrentThread() 之前调用是安全的。
static bool IsNestedOnCurrentThread();

// 针对当前线程管理嵌套的观察者,这样可以在RunLoop运行前和运行后都能收到通知,仅针对存在嵌套的RunLoop
static void AddNestingObserverOnCurrentThread(NestingObserver* observer);
static void RemoveNestingObserverOnCurrentThread(NestingObserver* observer);

// 在当前线程上注册 |delegate|。在使用 RunLoop 方法之前,必须在每个线程上调用一次且仅一次。从那时起,|delegate| 将永远与该线程绑定(包括其销毁)。
static void RegisterDelegateForCurrentThread(Delegate* delegate);

// 终止活动的 RunLoop(在空闲时)-- 必须存在一个。这些方法被引入作为长期弃用的 MessageLoop::Quit(WhenIdle)(Closure) 方法的首选临时替代方法。调用者应该正确地传递对应的 RunLoop 实例(或其 QuitClosure)的引用,而不是使用这些方法,以将 Run()/Quit() 链接到单个 RunLoop 实例并提高可读性。
static void QuitCurrentDeprecated();
static void QuitCurrentWhenIdleDeprecated();
static RepeatingClosure QuitCurrentWhenIdleClosureDeprecated();

成员变量

代码语言:C++
复制
// RunLoop::Delegate 的缓存引用,用于驱动此 RunLoop 的线程,以便快速访问而无需使用 TLS(还允许在 Run() 过程中从另一个序列访问状态,参考下面的 |sequence_checker_|)。
Delegate* const delegate_;

const Type type_;

#if DCHECK_IS_ON()
bool run_called_ = false;
#endif

bool quit_called_ = false;
bool running_ = false;
// 用于记录在此 RunLoop 上调用了 QuitWhenIdle(),意味着 Delegate 应该在变为空闲时退出 Run()(它负责通过 ShouldQuitWhenIdle() 探测此状态)。此状态存储在这里而不是推送到 Delegate,以支持嵌套的 RunLoops。
bool quit_when_idle_received_ = false;

// 如果允许使用 QuitCurrentDeprecated(),则为 true。从 RunLoop 中获取 QuitClosure() 会隐式将其设置为 false,因此在该 RunLoop 运行时不能使用 QuitCurrent*Deprecated()。
bool allow_quit_current_deprecated_ = true;

// RunLoop 不是线程安全的。除非标记为线程安全,否则其状态/方法不得从构造它的线程以外的任何序列访问。例外情况:在 Run() 过程中可以安全地从另一个序列(或单个并行任务)访问 RunLoop -- 例如,在测试中不必在整个测试中传递 ThreatTaskRunnerHandle::Get() 来重新发布 QuitClosure 到原始线程。
SEQUENCE_CHECKER(sequence_checker_);

const scoped_refptr<SingleThreadTaskRunner> origin_task_runner_;

// QuitClosure 的 WeakPtrFactory,用于安全性。
WeakPtrFactory<RunLoop> weak_factory_{ this };

DISALLOW_COPY_AND_ASSIGN(RunLoop);

核心实现

BeforeRun
代码语言:C++
复制
bool RunLoop::BeforeRun() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

#if DCHECK_IS_ON()
  DCHECK(delegate_->allow_running_for_testing_)
      << "RunLoop::Run() isn't allowed in the scope of a "
         "ScopedDisallowRunningForTesting. Hint: if mixing "
         "TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
         "API instead of RunLoop to drive individual task runners.";
  DCHECK(!run_called_);
  run_called_ = true;
#endif  // DCHECK_IS_ON()

  // Allow Quit to be called before Run.
  if (quit_called_)
    return false;

  auto& active_run_loops = delegate_->active_run_loops_;
  active_run_loops.push(this);

  const bool is_nested = active_run_loops.size() > 1;

  if (is_nested) {
    for (auto& observer : delegate_->nesting_observers_)
      observer.OnBeginNestedRunLoop();
    if (type_ == Type::kNestableTasksAllowed)
      delegate_->EnsureWorkScheduled();
  }

  running_ = true;
  return true;
}
AfterRun
代码语言:C++
复制
void RunLoop::AfterRun() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  running_ = false;

  auto& active_run_loops = delegate_->active_run_loops_;
  DCHECK_EQ(active_run_loops.top(), this);
  active_run_loops.pop();

  // Exiting a nested RunLoop?
  if (!active_run_loops.empty()) {
    for (auto& observer : delegate_->nesting_observers_)
      observer.OnExitNestedRunLoop();

    // Execute deferred Quit, if any:
    if (active_run_loops.top()->quit_called_)
      delegate_->Quit();
  }
}
Run

实际调用的RunWithTimeout(TimeDelta::Max());

代码语言:C++
复制
void RunLoop::Run() {
  RunWithTimeout(TimeDelta::Max());
}

void RunLoop::RunWithTimeout(TimeDelta timeout) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!BeforeRun())
    return;

  // If there is a ScopedRunTimeoutForTest active then set the timeout.
  // TODO(crbug.com/905412): Use real-time for Run() timeouts so that they
  // can be applied even in tests which mock TimeTicks::Now().
  CancelableOnceClosure cancelable_timeout;
  ScopedRunTimeoutForTest* run_timeout = ScopedRunTimeoutForTestTLS().Get();
  if (run_timeout) {
    cancelable_timeout.Reset(
        BindOnce(&OnRunTimeout, Unretained(this), run_timeout->on_timeout()));
    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
        FROM_HERE, cancelable_timeout.callback(), run_timeout->timeout());
  }

  DCHECK_EQ(this, delegate_->active_run_loops_.top());
  const bool application_tasks_allowed =
      delegate_->active_run_loops_.size() == 1U ||
      type_ == Type::kNestableTasksAllowed;
  delegate_->Run(application_tasks_allowed, timeout);

  AfterRun();
}

RunLoop with base::SimpleThread

RunLoop使用必须要继承RunLoop::Delegate来使用,又由于RunLoop需要绑定线程来进行事件循环,所以借助SimpleThread来创建事件循环

SimpleThread用法可以参考SimpleThread

代码语言:C++
复制
// 定义RunLoop必须的delegate
class MyRunLoopDelegate : public base::RunLoop::Delegate {
public:
    explicit MyRunLoopDelegate() {}
    ~MyRunLoopDelegate() override = default;

    void Run(bool application_tasks_allowed, base::TimeDelta timeout) override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };
    void Quit() override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };

    void EnsureWorkScheduled() override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };
};

// 定义SampleThread的基本运行逻辑
class SimpleDelegate : public base::DelegateSimpleThread::Delegate {
    // 基本结构
public:
    explicit SimpleDelegate() {}
    ~SimpleDelegate() override = default;
private:
    // 覆写继承的Run
    void Run() override {
        L_TRACE(L"%s", __FUNCTIONW__);
        // 做一个线程逻辑
            // 此时线程还没运行,我们创建RunLoop
        MyRunLoopDelegate runloop_delegate;
        base::RunLoop::RegisterDelegateForCurrentThread(&runloop_delegate);

        // 等注册了delegate函数后,可以创建run_loop了
        // 创建了RunLoop后可以
        base::RunLoop run_loop(base::RunLoop::Type::kDefault);

        run_loop.Run();
        L_TRACE(L"%s begin run loop", __FUNCTIONW__);
        base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
        L_TRACE(L"%s after sleep", __FUNCTIONW__);
        run_loop.Quit();
        L_TRACE(L"%s after quit = [%d]", __FUNCTIONW__, run_loop.running());
    }
};

int main() {
    // 创建SimpleThread
    SimpleDelegate delegate;
    base::DelegateSimpleThread thread(&delegate, "test run loop");

    thread.Start();
    L_TRACE(L"%s start", __FUNCTIONW__);
    thread.Join();
    L_TRACE(L"%s has join", __FUNCTIONW__);
}

运行报错

报错
报错

前文有说过,RunLoop执行任务能力是由创建时所处线程的TaskRunner来提供,所以SimpleThread是不具有TaskRunner的,才会报错,需要改用base::Thread

RunLoop with base::Thread

base::Thread可以参考:base::Thread

base::Thread自带RunLoop,所以再运行一个RunLoop则会出现嵌套RunLoop导致base::Thread永远无法退出,需要在第二个RunLoop这里做严格的管控

示意
示意

源码如下:

代码语言:C++
复制
// 定义RunLoop必须的delegate
class MyRunLoopDelegate : public base::RunLoop::Delegate {
public:
    explicit MyRunLoopDelegate() {}
    ~MyRunLoopDelegate() override = default;

    void Run(bool application_tasks_allowed, base::TimeDelta timeout) override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };
    void Quit() override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };

    void EnsureWorkScheduled() override {
        L_TRACE(L"%s", __FUNCTIONW__);
    };
};

void RegisterRunLoop() {
    // 在线程中创建RunLoop
    // 这里base::Thread已经有了delegate,不需要额外创建自定义的
    // MyRunLoopDelegate runloop_delegate;
    // base::RunLoop::RegisterDelegateForCurrentThread(&runloop_delegate);

    // 等注册了delegate函数后,可以创建run_loop了,如果是base::Thread,则不需要注册
    base::RunLoop run_loop(base::RunLoop::Type::kDefault);

    // 3秒后退出事件循环
    // base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::BindOnce(&base::RunLoop::Quit, base::Unretained(&run_loop)), base::TimeDelta::FromSeconds(3));
    // 这里的延时任务会因为下面的run_loop执行hung住当前线程导致无法执行,所以换成RunWithTimeout,或者另起线程来做退出的操作

    run_loop.RunWithTimeout(base::TimeDelta::FromSeconds(3)); // 这里会hung住当前线程,3秒自动退出事件循环
    L_TRACE(L"%s begin run loop", __FUNCTIONW__);
    base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
    L_TRACE(L"%s after sleep", __FUNCTIONW__);
    run_loop.Quit();
    L_TRACE(L"%s after quit = [%d]", __FUNCTIONW__, run_loop.running());
}

int main() {
    // 创建SimpleThread
    base::Thread thread("run loop test");
    thread.Start();
    // 线程创建RunLoop
    thread.task_runner()->PostTask(FROM_HERE, base::BindOnce(&RegisterRunLoop));

    // 等待所有任务完成
    thread.FlushForTesting();

    // 停止线程
    thread.Stop();
}

总结:一般情况RunLoop可以不用,除非有特殊的场景需要,主要是用来了解Chromium底层的多线程设计。

谢谢各位看到这里,如果有感兴趣的模块或者代码需要攻略,也可以留言,会不定时更新。喜欢可以去github点点赞,再次感谢🙏

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码
  • 前言
  • RunLoop
    • 为何要有这个?
    • 源码赏析
      • RunLoop::Delegate
        • Run和Quit
          • RunLoop::Type
            • 构造和析构
              • 运行
                • 终止
                  • 静态方法
                    • 成员变量
                      • 核心实现
                        • BeforeRun
                        • AfterRun
                        • Run
                    • RunLoop with base::SimpleThread
                    • RunLoop with base::Thread
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档