Chrome中的线程与任务

       原文链接:https://chromium.googlesource.com/chromium/src/+/master/docs/threading_and_tasks.md  

       Chrome具有多进程体系结构,并且每个进程都是高度多线程的。 在本文档中,我们将介绍每个进程共享的基本线程系统。 主要目标是使主线程(在浏览器进程中也称为“ UI”线程)和IO线程(用于处理IPC的每个进程的线程)保持响应。 这意味着将任何阻塞的I / O或其他昂贵的操作卸载到其他线程。 我们的方法是使用消息传递作为线程之间进行通信的方式。 我们不建议使用锁定和线程安全的对象。 相反,对象仅生活在一个(通常是虚拟的,我们稍后再讨论!)线程上,并且我们在这些线程之间传递消息进行通信。

核心概念 

  • Task:要处理的工作单元。有效地具有可选状态的函数指针。在Chrome中,这是通过base :: Bind(documentation)创建的base :: Callback。
  • Task queue:要处理的任务队列。
  • Physical thread:操作系统提供的线程(例如,POSIX上的pthread或Windows上的CreateThread())。 Chrome跨平台的抽象是base :: PlatformThread。您应该几乎永远不要直接使用它
  • base ::Thread:一个物理线程,永久地处理来自专用任务队列的消息,直到Quit()。您应该几乎永远不要创建自己的base :: Thread。
  • Thread pool:具有共享任务队列的物理线程池。在Chrome中,这是base :: ThreadPoolInstance。每个Chrome进程只有一个实例,它可以处理通过base / task / post_task.h发布的任务,因此您几乎不需要直接使用base :: ThreadPoolInstance API(有关稍后发布任务的更多信息)。
  • Sequence or Virtual thread:由chrome管理的执行线程。就像物理线程一样,在任何给定时刻,只有一个任务可以在给定序列/虚拟线程上运行,并且每个任务都可以看到先前任务的副作用。任务按顺序执行,但可能会在每个任务之间跳动物理线程。
  • Task runner:可以通过其发布任务的界面。在Chrome中,这是base :: TaskRunner。
  • Sequenced task runner:一个任务运行程序,它确保发布到其上的任务将按发布顺序顺序运行。保证每个这样的任务都会看到其前面的任务的副作用。发布到已排序任务运行器的任务通常由单个线程(虚拟或物理)处理。在Chrome中,这是base :: SequencedTaskRunner,它是base :: TaskRunner。
  • Single-thread task runner:顺序任务运行程序,可确保所有任务将由同一物理线程处理。在Chrome中,这是base :: SingleThreadTaskRunner,它是base :: SequencedTaskRunner。只要有可能,我们更喜欢序列而不是线程。

辅助理解:

  • Thread-unsafe:Chrome中的绝大多数类型都是线程不安全的(根据设计)。对此类类型/方法的访问必须在外部同步。通常,线程不安全类型要求将所有访问其状态的任务都发布到相同的base :: SequencedTaskRunner,并使用SEQUENCE_CHECKER成员在调试版本中对此进行验证。锁也是同步访问的一种方法,但是在Chrome浏览器中,我们强烈希望序列比锁更有效。
  • Thread-affine:此类类型/方法始终需要从相同的物理线程(即,从相同的base :: SingleThreadTaskRunner)访问,并且通常具有THREAD_CHECKER成员以验证它们是否正确。缺少使用第三方API或具有叶子依赖关系(Thread-affine)的原因:Chrome中几乎没有理由将类型设为Thread-affine。请注意,base :: SingleThreadTaskRunner是-base :: SequencedTaskRunner,因此Thread-affine是线程不安全的子集。Thread-affine有时也称为线程恶意。
  • Thread-safe:可以同时安全地访问此类类型/方法。
  • Thread-compatible:此类提供了对const方法的安全并发访问,但需要对非const(或混合const /非const访问)进行同步。 Chrome不会公开读写器锁;这样,唯一的用例是对象(通常是全局对象),这些对象以线程安全的方式(在启动的单线程阶段或通过线程安全的静态本地初始化范例lala base :: NoDestructor)并且永远不变。
  • Immutable:线程兼容类型的子集,构造后无法修改。
  • Sequence-friendly:此类类型/方法是线程不安全类型,支持从base :: SequencedTaskRunner调用。理想情况下,所有线程不安全类型都是这种情况.

线程数


每个Chrome进程都有

  • 一个主线程

在browser 进程(BrowserThread :: UI)中:更新UI

在renderer 进程(Blink主线程)中:运行大多数Blink

  • 一个IO线程

在browser 进程中(BrowserThread :: IO):处理IPC和网络请求

在renderer 进程中:处理IPC

  • 一些专用线程和通用线程池

大多数线程都有一个循环,该循环从队列中获取任务并运行它们(该队列可以在多个线程之间共享)。

在cef_type.h定义了进程名称和线程 名称

///
// Existing process IDs.
///
typedef enum {
  ///
  // Browser process.
  ///
  PID_BROWSER,
  ///
  // Renderer process.
  ///
  PID_RENDERER,
} cef_process_id_t;

///
// Existing thread IDs.
///
typedef enum {
  // BROWSER PROCESS THREADS -- Only available in the browser process.

  ///
  // The main thread in the browser. This will be the same as the main
  // application thread if CefInitialize() is called with a
  // CefSettings.multi_threaded_message_loop value of false. Do not perform
  // blocking tasks on this thread. All tasks posted after
  // CefBrowserProcessHandler::OnContextInitialized() and before CefShutdown()
  // are guaranteed to run. This thread will outlive all other CEF threads.
  ///
  TID_UI,

  ///
  // Used for blocking tasks (e.g. file system access) where the user won't
  // notice if the task takes an arbitrarily long time to complete. All tasks
  // posted after CefBrowserProcessHandler::OnContextInitialized() and before
  // CefShutdown() are guaranteed to run.
  ///
  TID_FILE_BACKGROUND,
  TID_FILE = TID_FILE_BACKGROUND,

  ///
  // Used for blocking tasks (e.g. file system access) that affect UI or
  // responsiveness of future user interactions. Do not use if an immediate
  // response to a user interaction is expected. All tasks posted after
  // CefBrowserProcessHandler::OnContextInitialized() and before CefShutdown()
  // are guaranteed to run.
  // Examples:
  // - Updating the UI to reflect progress on a long task.
  // - Loading data that might be shown in the UI after a future user
  //   interaction.
  ///
  TID_FILE_USER_VISIBLE,

  ///
  // Used for blocking tasks (e.g. file system access) that affect UI
  // immediately after a user interaction. All tasks posted after
  // CefBrowserProcessHandler::OnContextInitialized() and before CefShutdown()
  // are guaranteed to run.
  // Example: Generating data shown in the UI immediately after a click.
  ///
  TID_FILE_USER_BLOCKING,

  ///
  // Used to launch and terminate browser processes.
  ///
  TID_PROCESS_LAUNCHER,

  ///
  // Used to process IPC and network messages. Do not perform blocking tasks on
  // this thread. All tasks posted after
  // CefBrowserProcessHandler::OnContextInitialized() and before CefShutdown()
  // are guaranteed to run.
  ///
  TID_IO,

  // RENDER PROCESS THREADS -- Only available in the render process.

  ///
  // The main thread in the renderer. Used for all WebKit and V8 interaction.
  // Tasks may be posted to this thread after
  // CefRenderProcessHandler::OnRenderThreadCreated but are not guaranteed to
  // run before sub-process termination (sub-processes may be killed at any time
  // without warning).
  ///
  TID_RENDERER,
} cef_thread_id_t;

        在Browser进程中包含如下主要的线程:

TID_UI 线程是浏览器的主线程。如果应用程序在调用调用CefInitialize()时,传递CefSettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。
TID_IO 线程主要负责处理IPC消息以及网络通信。
TID_FILE 线程负责与文件系统交互。
        由于CEF采用多线程架构,有必要使用锁和闭包来保证数据的线程安全语义。IMPLEMENT_LOCKING定义提供了Lock()和 Unlock()方法以及AutoLock对象来保证不同代码块同步访问数据。CefPostTask函数组支持简易的线程间异步消息传递。

       可以通过CefCurrentlyOn()方法判断当前所在的线程环境,cefclient工程使用下面的定义来确保方法在期望的线程中被执行。

#define REQUIRE_UI_THREAD()   ASSERT(CefCurrentlyOn(TID_UI));
#define REQUIRE_IO_THREAD()   ASSERT(CefCurrentlyOn(TID_IO));
#define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));

 

任务


        任务是将base :: OnceClosure添加到队列中以进行异步执行

        base :: OnceClosure存储函数指针和参数。 它具有Run()方法,该方法使用绑定的参数调用函数指针。 它是使用base :: BindOnce创建的。 (请参阅Callback <>和Bind()文档)。

可以通过以下方式之一执行一组任务:

  • 并行:无任务执行顺序,可能在任何线程上一次全部执行
  • 已排序:以发布顺序执行的任务,在任何线程上一次执行。
  • 单线程:以发布顺序执行的任务,一次在一个线程上执行。
  • COM Single Threaded:COM初始化的单线程变体。

优先选择序列而不是物理线程


        顺序执行(在虚拟线程上)比单线程执行(在物理线程上)更受青睐。 除了绑定到主线程(UI)或IO线程的类型/方法外:通过base :: SequencedTaskRunner比通过管理自己的物理线程更好地实现了线程安全(请参见下面的发布顺序任务)。

发布并行任务


直接发布到线程池


        可以在任何线程上运行并且与其他任务没有排序或互斥要求的任务应使用base / task / thread_pool.h中定义的base :: ThreadPool :: PostTask *()函数之一发布。

base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(&Task));

        这将发布具有默认特征的任务。

       base :: ThreadPool :: PostTask *()函数允许调用者通过TaskTraits提供有关任务的其他详细信息(请参阅使用TaskTraits注释任务)。

base::ThreadPool::PostTask(
    FROM_HERE, {base::TaskPriority::BEST_EFFORT, MayBlock()},
    base::BindOnce(&Task));

通过TaskRunner发布


       并行的base :: TaskRunner是直接调用base :: ThreadPool :: PostTask *()的替代方法。 如果事先不知道是将任务并行发布,按顺序发布还是发布到单线程中(例如,发布顺序任务,将多个任务发布到同一线程),这将非常有用。 由于base :: TaskRunner是base :: SequencedTaskRunner和base :: SingleThreadTaskRunner的基类,所以scoped_refptr <TaskRunner>成员可以容纳base :: TaskRunner,base :: SequencedTaskRunner或base :: SingleThreadTaskRunner。

class A {
 public:
  A() = default;

  void PostSomething() {
    task_runner_->PostTask(FROM_HERE, base::BindOnce(&A, &DoSomething));
  }

  void DoSomething() {
  }

 private:
  scoped_refptr<base::TaskRunner> task_runner_ =
      base::ThreadPool::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
};

        除非测试需要精确控制任务的执行方式,否则最好直接调用base :: ThreadPool :: PostTask *()(请参见测试,以较弱的方式控制测试中的任务)。

发布顺序任务


         序列是一组任务,它们按发布顺序一次运行(不必在同一线程上)。 要将任务发布为序列的一部分,请使用base :: SequencedTaskRunner。

发布到新序列
base :: SequencedTaskRunner可以通过base :: ThreadPool :: CreateSequencedTaskRunner()创建。

scoped_refptr<SequencedTaskRunner> sequenced_task_runner =
    base::ThreadPool::CreateSequencedTaskRunner(...);

// TaskB runs after TaskA completes.
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
sequenced_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));

发布到当前(虚拟)线程


        首选的发布到当前(虚拟)线程的方法是通过base :: SequencedTaskRunnerHandle :: Get()。

// The task will run on the current (virtual) thread's default task queue.
base::SequencedTaskRunnerHandle::Get()->PostTask(
    FROM_HERE, base::BindOnce(&Task);

        请注意,SequencedTaskRunnerHandle :: Get()返回当前虚拟线程的默认队列。 在具有多个任务队列(例如BrowserThread :: UI)的线程上,该队列可以与当前任务所属的队列不同。 “当前”任务运行程序有意不通过a static getter暴露。 您已经知道它并且可以直接发布到它,或者您不知道,唯一明智的目的地是默认队列。

使用序列代替锁


        在Chrome中不鼓励使用锁。 序列固有地提供线程安全性。 优先选择始终从相同序列访问的类,而不是使用锁来管理自己的线程安全。

       线程安全的,但不是线程仿射的; 为何如此? 按相同顺序发布的任务将按顺序运行。 排序任务完成后,下一个任务可能会由其他工作线程执行,但可以确保该任务看到由其序列上的前一个任务引起的任何副作用。

class A {
 public:
  A() {
    // Do not require accesses to be on the creation sequence.
    DETACH_FROM_SEQUENCE(sequence_checker_);
  }

  void AddValue(int v) {
    // Check that all accesses are on the same sequence.
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    values_.push_back(v);
}

 private:
  SEQUENCE_CHECKER(sequence_checker_);

  // No lock required, because all accesses are on the
  // same sequence.
  std::vector<int> values_;
};

A a;
scoped_refptr<SequencedTaskRunner> task_runner_for_a = ...;
task_runner_for_a->PostTask(FROM_HERE,
                      base::BindOnce(&A::AddValue, base::Unretained(&a), 42));
task_runner_for_a->PostTask(FROM_HERE,
                      base::BindOnce(&A::AddValue, base::Unretained(&a), 27));

// Access from a different sequence causes a DCHECK failure.
scoped_refptr<SequencedTaskRunner> other_task_runner = ...;
other_task_runner->PostTask(FROM_HERE,
                            base::BindOnce(&A::AddValue, base::Unretained(&a), 1));

        锁仅应用于交换可以在多个线程上访问的共享数据结构。 如果一个线程基于昂贵的计算或通过磁盘访问来更新它,那么应该在不持有锁的情况下完成缓慢的工作。 仅当结果可用时,才应使用锁来交换新数据。 例如在PluginList :: LoadPlugins(content / browser / plugin_list.cc。如果必须使用锁,则应避免一些最佳做法和陷阱。

        为了编写非阻塞代码,Chrome中的许多API都是异步的。 通常,这意味着它们要么需要在特定的线程/序列上执行,并且将通过自定义委托接口返回结果,要么它们采用base :: Callback <>对象,该对象在请求的操作完成时被调用。 上面的PostTask部分中介绍了在特定线程/序列上执行工作。

将多个任务发布到同一线程


        如果多个任务需要在同一线程上运行,请将其发布到base :: SingleThreadTaskRunner。 所有发布到同一base :: SingleThreadTaskRunner的任务均按发布顺序在同一线程上运行。

        在浏览器进程中发布到主线程或IO线程
        要将任务发布到主线程或IO线程,请使用content / public / browser / browser_thread.h中的content :: GetUIThreadTaskRunner({})或content :: GetIOThreadTaskRunner({})

base::PostTask(FROM_HERE, {content::BrowserThread::UI}, ...);

base::CreateSingleThreadTaskRunner({content::BrowserThread::IO})
    ->PostTask(FROM_HERE, ...);

        主线程和IO线程已经非常忙。 因此,在可能的情况下,最好将其发布到通用线程中(请参阅发布并行任务,发布顺序任务)。 发布到主线程的充分理由是更新UI或访问绑定到主线程的对象(例如Profile)。 发布到IO线程的一个很好的理由是访问与其绑定的组件的内部(例如IPC,网络)。 注意:不必对IO线程进行明确的发布任务即可在网络上发送/接收IPC或发送/接收数据。

在render进程中发布到主线程


待办事项(blink-dev)

发布到自定义SingleThreadTaskRunner


        如果多个任务需要在同一线程上运行,并且该线程不必是主线程或IO线程,则将它们发布到base :: Threadpool :: CreateSingleThreadTaskRunner创建的base :: SingleThreadTaskRunner。

scoped_refptr<SingleThreadTaskRunner> single_thread_task_runner =
    base::Threadpool::CreateSingleThreadTaskRunner(...);

// TaskB runs after TaskA completes. Both tasks run on the same thread.
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskA));
single_thread_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskB));

发布到当前线程


        重要说明:要发布需要与当前任务序列相互排斥但不必绝对需要在当前物理线程上运行的任务,请使用base :: SequencedTaskRunnerHandle :: Get()而不是base :: ThreadTaskRunnerHandle :: Get() )(请参阅发布到当前序列)。 这样可以更好地记录发布任务的要求,并避免不必要地使您的API物理线程仿射。 在单线程任务中,base :: SequencedTaskRunnerHandle :: Get()等效于base :: ThreadTaskRunnerHandle :: Get()。
如果仍然必须将任务发布到当前的物理线程,请使用base :: ThreadTaskRunnerHandle。

// The task will run on the current thread in the future.
base::ThreadTaskRunnerHandle::Get()->PostTask(
    FROM_HERE, base::BindOnce(&Task));

将任务发布到COM单线程单元(STA)线程(Windows)


        必须在COM单线程单元(STA)线程上运行的任务必须发布到base :: ThreadPool :: CreateCOMSTATaskRunner()返回的base :: SingleThreadTaskRunner。 如将多个任务发布到同一线程中提到的,所有发布到同一base :: SingleThreadTaskRunner的任务均按发布顺序在同一线程上运行。

// Task(A|B|C)UsingCOMSTA will run on the same COM STA thread.

void TaskAUsingCOMSTA() {
  // [ This runs on a COM STA thread. ]

  // Make COM STA calls.
  // ...

  // Post another task to the current COM STA thread.
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(&TaskCUsingCOMSTA));
}
void TaskBUsingCOMSTA() { }
void TaskCUsingCOMSTA() { }

auto com_sta_task_runner = base::ThreadPool::CreateCOMSTATaskRunner(...);
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskAUsingCOMSTA));
com_sta_task_runner->PostTask(FROM_HERE, base::BindOnce(&TaskBUsingCOMSTA));

 

使用TaskTraits注释任务


         base :: TaskTraits封装有关任务的信息,该信息可帮助线程池做出更好的调度决策。

        当默认特征足够时,可以传递采用base :: TaskTraits的方法{}。 默认特征适用于以下任务:

  •         不要阻止(参考MayBlock和WithBaseSyncPrimitives);
  •         与阻止用户活动有关; (显式或隐式地通过对具有此功能的组件具有排序依赖性)
  •         可以阻止关闭,也可以在关闭时跳过(线程池可以自由选择合适的默认值)。 与该描述不匹配的任务必须使用明确的TaskTraits发布。

base / task / task_traits.h提供了可用特征的详尽文档。 内容层还在content / public / browser / browser_task_traits.h中提供了其他特征,以便于将任务发布到BrowserThread上。

以下是一些如何指定base :: TaskTraits的示例。

// This task has no explicit TaskTraits. It cannot block. Its priority is
// USER_BLOCKING. It will either block shutdown or be skipped on shutdown.
base::ThreadPool::PostTask(FROM_HERE, base::BindOnce(...));

// This task has the highest priority. The thread pool will schedule it before
// USER_VISIBLE and BEST_EFFORT tasks.
base::ThreadPool::PostTask(
    FROM_HERE, {base::TaskPriority::USER_BLOCKING},
    base::BindOnce(...));

// This task has the lowest priority and is allowed to block (e.g. it
// can read a file from disk).
base::ThreadPool::PostTask(
    FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
    base::BindOnce(...));

// This task blocks shutdown. The process won't exit before its
// execution is complete.
base::ThreadPool::PostTask(
    FROM_HERE, {base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
    base::BindOnce(...));

 

保持浏览器的响应

        不要在主线程,IO线程或任何期望以低延迟运行任务的序列上执行耗时的工作。 而是使用base :: ThreadPool :: PostTaskAndReply *()或base :: SequencedTaskRunner :: PostTaskAndReply()异步执行耗时的工作。 请注意,IO线程上的异步/重叠I / O很好。示例:在主线程上运行以下代码将阻止浏览器长时间响应用户输入。

// GetHistoryItemsFromDisk() may block for a long time.
// AddHistoryItemsToOmniboxDropDown() updates the UI and therefore must
// be called on the main thread.
AddHistoryItemsToOmniboxDropdown(GetHistoryItemsFromDisk("keyword"));

         下面的代码通过在线程池中安排对GetHistoryItemsFromDisk()的调用,然后在原始序列(在本例中为主线程)上对AddHistoryItemsToOmniboxDropdown()的调用来解决此问题。 第一次调用的返回值将自动作为第二次调用的参数提供。

base::ThreadPool::PostTaskAndReplyWithResult(
    FROM_HERE, {base::MayBlock()},
    base::BindOnce(&GetHistoryItemsFromDisk, "keyword"),
    base::BindOnce(&AddHistoryItemsToOmniboxDropdown));

延迟发布任务


延迟发布一次性任务
        要发布必须在延迟到期后运行一次的任务,请使用base :: ThreadPool :: PostDelayedTask *()或base :: TaskRunner :: PostDelayedTask()。

base::ThreadPool::PostDelayedTask(
  FROM_HERE, {base::TaskPriority::BEST_EFFORT}, base::BindOnce(&Task),
  base::TimeDelta::FromHours(1));

scoped_refptr<base::SequencedTaskRunner> task_runner =
    base::ThreadPool::CreateSequencedTaskRunner(
        {base::TaskPriority::BEST_EFFORT});
task_runner->PostDelayedTask(
    FROM_HERE, base::BindOnce(&Task), base::TimeDelta::FromHours(1));

注意:延迟1小时的任务在延迟时间到期后可能不必立即运行。 指定base :: TaskPriority :: BEST_EFFORT以防止其延迟到期后降低浏览器的速度。

延迟发布重复任务


要发布必须定期运行的任务,请使用base :: RepeatingTimer。

class A {
 public:
  ~A() {
    // The timer is stopped automatically when it is deleted.
  }
  void StartDoingStuff() {
    timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1),
                 this, &MyClass::DoStuff);
  }
  void StopDoingStuff() {
    timer_.Stop();
  }
 private:
  void DoStuff() {
    // This method is called every second on the sequence that invoked
    // StartDoingStuff().
  }
  base::RepeatingTimer timer_;
};

取消任务


使用base :: WeakPtr
base :: WeakPtr可用于确保在销毁对象时取消取消绑定到该对象的任何回调。

int Compute() { … }

class A {
 public:
  void ComputeAndStore() {
    // Schedule a call to Compute() in a thread pool followed by
    // a call to A::Store() on the current sequence. The call to
    // A::Store() is canceled when |weak_ptr_factory_| is destroyed.
    // (guarantees that |this| will not be used-after-free).
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, base::BindOnce(&Compute),
        base::BindOnce(&A::Store, weak_ptr_factory_.GetWeakPtr()));
  }

 private:
  void Store(int value) { value_ = value; }

  int value_;
  base::WeakPtrFactory<A> weak_ptr_factory_{this};
};

注意:WeakPtr不是线程安全的:GetWeakPtr(),〜WeakPtrFactory()和Compute()(绑定到WeakPtr)必须都在同一序列上运行。

使用base :: CancelableTaskTracker
base :: CancelableTaskTracker允许取消的顺序与执行任务的顺序不同。 请记住,CancelableTaskTracker无法取消已经开始运行的任务。

auto task_runner = base::ThreadPool::CreateTaskRunner({});
base::CancelableTaskTracker cancelable_task_tracker;
cancelable_task_tracker.PostTask(task_runner.get(), FROM_HERE,
                                 base::DoNothing());
// Cancels Task(), only if it hasn't already started running.
cancelable_task_tracker.TryCancelAll();

发布作业以并行运行


        base :: PostJob是高级用户API,能够安排单个base :: RepeatingCallback工作程序任务并请求ThreadPool工作程序同时调用它。 这避免了简并的情况:

        为每个工作项调用PostTask(),导致大量开销。
        固定数量的PostTask()调用可拆分工作,并且可能会运行很长时间。 当许多组件发布“ num cores”任务并且所有人都希望使用所有内核时,这将成为问题。 在这些情况下,调度程序缺乏对多个相同优先级请求公平的上下文和/或在出现高优先级工作时无法请求较低优先级工作来产生的能力。
有关完整示例,请参见base / task / job_perftest.cc。

// A canonical implementation of |worker_task|.
void WorkerTask(base::JobDelegate* job_delegate) {
  while (!job_delegate->ShouldYield()) {
    auto work_item = TakeWorkItem(); // Smallest unit of work.
    if (!work_item)
      return:
    ProcessWork(work_item);
  }
}

// Returns the latest thread-safe number of incomplete work items.
void NumIncompleteWorkItems();

base::PostJob(FROM_HERE, {},
              base::BindRepeating(&WorkerTask),
              base::BindRepeating(&NumIncompleteWorkItems));

        通过在调用时在循环中进行尽可能多的工作,工作程序任务避免了调度开销。 同时,定期调用base :: JobDelegate :: ShouldYield()有条件地退出并让调度程序对其他工作进行优先级排序。 例如,这种收益语义允许用户可见的作业使用所有内核,但是当出现用户阻塞任务时,就可以避免使用。

        向正在运行的作业中添加其他工作


         当添加新的工作项并且API用户希望其他线程同时调用工作程序任务时,必须在最大并发性增加后不久调用JobHandle / JobDelegate :: NotifyConcurrencyIncrease()。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python可以使用多线程来启动Chrome浏览器。我们可以利用selenium库来控制浏览器的行为,并结合多线程来实现同时启动多个Chrome浏览器实例。 首先,我们需要安装selenium库,可以使用pip命令进行安装。安装完成后,我们需要下载对应版本的Chrome浏览器驱动,然后将驱动的路径添加到系统的环境变量。 接下来,我们可以编写一个多线程的函数,用于启动Chrome浏览器。首先导入selenium库的webdriver模块,然后在函数创建一个webdriver对象,设置浏览器的选项,如窗口大小、启动位置等,最后使用get()方法打开要访问的网页。 然后,我们可以编写一个主函数,创建多个线程来分别执行启动浏览器的任务。可以使用threading库的Thread类来创建线程对象,并使用start()方法来启动线程。 最后,在主函数使用join()方法使主线程等待所有子线程执行完毕,然后关闭Chrome浏览器。 下面是一个示例代码: ```python import threading from selenium import webdriver def open_browser(): driver = webdriver.Chrome() # 创建Chrome浏览器对象 driver.maximize_window() # 设置浏览器窗口大小为最大化 driver.get("https://www.example.com") # 打开要访问的网页 def main(): threads = [] # 存放线程列表 for i in range(5): # 启动5个线程 t = threading.Thread(target=open_browser) # 创建线程对象 threads.append(t) # 添加到线程列表 t.start() # 启动线程 for t in threads: t.join() # 等待所有线程执行完毕 driver.quit() # 关闭Chrome浏览器 if __name__ == "__main__": main() ``` 以上就是使用Python多线程来启动Chrome浏览器的简单示例。通过多线程的方式,我们可以同时启动多个Chrome浏览器实例,提高程序的执行效率。 ### 回答2: 在Python启动Chrome浏览器并使用多线程可以通过以下步骤完成。 首先,我们需要安装并导入selenium库,它是一种Web自动化测试工具,可以与Chrome浏览器进行交互。 接下来,我们需要下载Chrome驱动程序,该驱动程序用于控制和操作Chrome浏览器。下载完驱动程序后,我们需要将其添加到系统路径,以便Python可以找到它。 然后,我们可以使用selenium库的webdriver模块来创建浏览器对象。在创建浏览器对象时,我们可以通过指定Chrome驱动程序的路径来告诉Python要使用Chrome浏览器。 接下来,我们可以使用创建的浏览器对象来打开一个URL,并执行我们想要的操作,例如点击按钮,填写表单等等。 最后,我们可以在一个多线程的环境启动多个线程,每个线程创建一个浏览器对象并执行相应的操作。 以下是一个示例代码,演示了如何使用多线程启动Chrome浏览器: ```python from selenium import webdriver import threading def open_browser(url): # 创建浏览器对象 driver = webdriver.Chrome("path/to/chromedriver") # 打开URL并执行相应操作 driver.get(url) # 进行其他操作... # 关闭浏览器 driver.quit() # 设置要打开的URL列表 urls = ["https://www.example1.com", "https://www.example2.com", "https://www.example3.com"] # 创建并启动多个线程 threads = [] for url in urls: thread = threading.Thread(target=open_browser, args=(url,)) thread.start() threads.append(thread) # 等待所有线程结束 for thread in threads: thread.join() ``` 在上面的代码,我们使用了一个开放浏览器的函数`open_browser`,它接受一个URL作为参数并在浏览器打开该URL。我们创建了一个URL列表,并使用多线程启动多个线程,每个线程都打开一个URL。最后,我们等待所有线程结束。 以上就是使用多线程启动Chrome浏览器的基本步骤和示例代码。希望对你有所帮助! ### 回答3: Python多线程是一种可以在同一程序同时运行多个线程的机制。通过多线程,我们可以在一个程序同时执行多个任务,提高程序的运行效率。而启动Chrome是指通过Python脚本来启动Chrome浏览器。 在Python,我们可以使用threading模块来实现多线程编程。首先,需要导入threading模块,然后使用Thread类创建一个线程对象。接下来,我们可以定义一个函数,作为线程的执行体。在这个函数,可以编写启动Chrome浏览器的代码。 启动Chrome浏览器可以使用selenium库,它提供了对多种浏览器的支持。通过selenium库的webdriver模块,我们可以创建一个浏览器对象,并在代码进行相应的操作。 下面是一个简单的示例代码: ``` import threading from selenium import webdriver def start_chrome(): driver = webdriver.Chrome() # 创建Chrome浏览器对象 driver.get("https://www.example.com") # 打开指定网址 # 进行其他操作,如点击按钮、填写表单等 driver.quit() # 关闭Chrome浏览器 # 创建一个线程对象,并指定要执行的函数 thread = threading.Thread(target=start_chrome) # 启动线程 thread.start() ``` 在这个示例,我们使用threading模块创建了一个线程对象,并指定了要执行的函数为`start_chrome`。然后,使用`start()`方法启动线程,线程会自动执行`start_chrome`函数的代码。 在`start_chrome`函数,我们首先创建了一个Chrome浏览器对象,然后使用`get()`方法打开了一个网址,并进行其他操作。最后,使用`quit()`方法关闭Chrome浏览器。 总的来说,通过多线程机制可以在Python同时启动Chrome浏览器,从而实现并发执行多个任务的效果。这样可以提高程序的效率,并且方便进行一些自动化的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值