zoukankan      html  css  js  c++  java
  • threading

    概览:

         Chromium是一个有着诸多线程的产品。我们尽量保证UI的快速响应,这就意味着不能有任何的堵塞的I/O或者耗时操作来堵塞UI线程。我们把在线程间传递消息作为线程间通信的方法。我们摒弃了锁和线程安全对象,取而代之的是一个对象只生存在一个线程中,我们通过在线程间传递消息来通讯,并且我们使用回掉接口(由发送消息者实现)来完成线程间的请求。

         Thread在base/thread.h中定义,我们已经有很多线程以至难以跟踪了,大体上你可以使用一个下面列表中已有的线程而不是新建一个线程。每个线程都有一个MessageLoop(参考:base/message_loop.h)为线程处理消息。你可以通过Thread.message_loop()来获取消息循环。

         大部分的线程都是由BrowserProcess对象来管理的,BrowserProcess在“browser”主进程中扮演者服务管理者的角色。一般情况下,所有事件都是发生在UI线程(应用程序启动的主线程)。我么把具体的某个处理类内置到了其他线程,它(BrowserProcess)具有以下线程的getter方法​​:

    • io_thread:该线程并不名副其实。它其实是个派发线程,它负责浏览器主线程和其他子线程的通信。它也是所有资源请求(网页加载)的来源。
    • file_thread:文件操作的线程。当你要执行一个阻塞的文件系统操作(例如,加载一个文件中的icon,或者写入下载的文件),发送消息到该线程。
    • db_thread:数据库操作的线程。例如,cookie服务会在该线程执行数据库操作。注意:历史记录数据库不在该线程操作。
    • safe_browsing_thread

    几个组建由他们自己的线程:

    • History:历史记录对象有自己的线程。看起来可以和上面的数据库线程合并。然而,我们需要保证某些事件按照正确的顺序执行--例如:cookies由于第一次启动的时候需要比历史先加载,并且加载历史十分耗时会阻塞住。
    • ProxyServices:可以查阅net/http/http_proxy_service.cc。
    • Automation proxy:测试程序通过该线程与浏览器通信,控制浏览器行为。

    保持浏览器及时响应

      正如在概述中提示的,我们极力的避免在UI线程做阻塞的I/O操作来保持UI的及时响应。更加需要注意的是我们也需要尽量避免在IO线程做阻塞的I/O操作。如果我们阻塞住,意味着磁盘操作,然后IPC消息也无法得到处理。表现就是用户无法与页面交互。需要注意的是异步/重叠 IO是个不错的选择。

      另外需要注意的是线程之间不要互相有阻塞的操作。索只有在多个线程共享交换数据的结构中使用。这样,如果一个线程更新有一个很耗计算的操作或者文件访问就不会等待解锁。只有当结果是可预期的时候才可以用锁来完成数据交换。一个例子是Plugin::LoadPlugins(src/webkit/glue/plugins/plugin_list.cc),如果你必须使用锁,这里有很好的练习以防调入陷阱。

      为了写出非阻塞的代码,很多API在chromium都是异步的。通常情况下,这就意味着要么需要在特定的线程执行然后通过定制的委托接口吧结果传回来,要么在请求结束的时候调用base::CallBack<>对象。在指定线程执行任务在PostTask的部分有具体描述。

    Getting stuff to other threads

    base::CallBack<>,Async APIs,and Currying

    base::CallBack<>(参见docs in callback.h)是一个模版类,内部有个Run()方法。他是一个全局的函数指针,在调用base::Bind的时候创建。异步Apis经常会把base::CallBack<>作为异步返回操作结果的方法。下面是一个假想的异步读取文件的API:

    void ReadToString(const std::string& filename, const base::Callback<void(const std::string&)>& on_read);
    
    void DisplayString(const std::string& result) {
      LOG(INFO) << result;
    }
    
    void SomeFunc(const std::string& file) {
      ReadToString(file, base::Bind(&DisplayString));
    };

    在上面的例子中,base::Bind 以&DisplayString为参数,并且转化为一个base::Callback<void(const std::string& result)>.类型base::Callback<>是通过参数推断的。为何不直接传递函数指针呢?原因就是base::Bind 允许调用者适应函数接口通过Currying (http://en.wikipedia.org/wiki/Currying)增加一些额外的内容。例如,如果有一个函数DisplayStringWithPrefix 有些额外的参数作为前缀,我们通过base::Bind适应下面的接口:

    void DisplayStringWithPrefix(const std::string& prefix, const std::string& result) {
      LOG(INFO) << prefix << result;
    }
    
    void AnotherFunc(const std::string& file) {
      ReadToString(file, base::Bind(&DisplayStringWithPrefix, "MyPrefix: "));
    };

    这样可以用来替代通过在类中以成员变量保存前缀变量并创建一个适配的函数的方法。另外要注意“MyPrifx:”参数实际上是一个const char *,然而DisplayStringWithPrefix 实际上是需要const std::string& 如同通常的函数派发,如果可以的话base::bind会强制转换参数类型。查阅下面“How arguments are handled by base::Bind()”关于详细的参数保存,拷贝,引用。


    PostTask

      派发消息到其他线程最初级的方法就是使用MessageLoop.PostTask和MessageLoop.PostDelayTask(参阅base/message_loop.h) PostTask安排任务在指定的线程上执行。任务需要定义成base::Closure类型,它是typedef的base::Callback<void(void)>.PostDelayedTask安排一个任务在指定线程延迟执行。一个任务就是拥有一个Run()方法的base::Closure,通过调用base::Bind()创建。消息循环通过调用Run方法来执行任务,然后减少对task对象的引用。PostTask和PostDelayTask都有一个参数tracked_objects::Location,它用来实现一个轻量级的调试(统计刮起和任务完成的性能,在debug版本中通过url about:objects查看),通常该参数用FROME_HERE宏来代替。

      需要注意的是新的任务运行在消息循环依赖的队列中,所指定的任何延误是操作系统的定时器决定的。这就意味着,在windows下,一个很短的延时(小于10ms)会不精确(实际上会更大一些)。使用PostDelayTask延迟0执行和直接PostTask效果一样。PostTask也经常会用来给当前线程投递任务“有时候在当前处理返回到消息循环的时候”这样的一个延续,可用于在当前线程上,以确保其他时间的关键任务不会被饿死在此线程。

      下面是一个例子,掩饰如何创建一个任务,然后发送到另外一个线程(本例中是file thread):
    void WriteToFile(const std::string& filename, const std::string& data);
    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    base::Bind(&WriteToFile, "foo.txt", "hello world!"))


      你可以使用BrowserThread来在线程间发送任务。永远不要缓存MessageLoop指针,因为会导致bug,例如你还持有指针,但是它已经被删除了。更多内容请参考这里


    base::Bind() and class method
    base::Bind() API也支持类成员函数。写法类似普通函数,除了第一个参数必须是该方法所属的对象。一般情况向,这个对象必须是 thread-safe reference-counted。引用技术确保在其他线程调用的时候该对象依然或者,知道任务完成。

    class MyObject : public base::RefCountedThreadSafe<MyObject> {
    public:
    void DoSomething(const std::string16& name) {
    thread_->message_loop()->PostTask(
    FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, this, name));
      }

    void DoSomethingOnAnotherThread(const std::string16& name) {
    ...
    }
     private:
    // Always good form to make the destructor private so that only RefCountedThreadSafe can access it.
    // This avoids bugs with double deletes.
    friend class base::RefCountedThreadSafe<MyObject>;

    ~MyObject();
      Thread* thread_;
    };

    如果你有外部同步化的结构,它可以确保任务在执行的时候保持对象存活,那么你可以使用base::Unretained()包装对象指针,使得调用base::Bind()的时候禁用引用计数。这个也允许使用在非引用计数的类上,请小心使用!

    how arguments are handled by base::Bind()

      参数拷贝到了内部的结构体(InvokeStorage)中(base/bind_internal中定义)。函数调用的时候他会拷贝参数。如果方法中有参数是常引用,这个引用会指向拷贝的参数。如果需要只想原始参数的引用,可以使用base::ConstRef()。要小心使用,例如在传递参数的时候不能保证存活就很危险。尤其是永远不要对栈上的变量使用base::ConstRef()除非你能保证在异步调用完成前栈帧保持有效。

      有时候你会想以一个引用计数的对象作为参数(保证使用的是从RefCountedThreadSafe集成的类型,而不是简单的从RefCounted继承的类型)。为了确保对象的生存期能够覆盖整个请求,Closure需要保有一个对象的指针。这可以通过scoped_refptr作为参数类型,或者用make_scoped_refptr()包装一个原始的指针实现:
      
    class SomeParamObject : public base::RefCountedThreadSafe<SomeParamObject> {
    ...
    };

    class MyObject : public base::RefCountedThreadSafe<MyObject> {
    public:
    void DoSomething() {
    scoped_refptr<SomeParamObject> param(new SomeParamObject);
    thread_->message_loop()->PostTask(FROM_HERE
    base::Bind(&MyObject::DoSomethingOnAnotherThread, this, param)); }
      void DoSomething2() {
    SomeParamObject* param = new SomeParamObject;
    thread_->message_loop()->PostTask(FROM_HERE
    base::Bind(&MyObject::DoSomethingOnAnotherThread, this, make_scoped_refptr(param)));
      }
      // Note how this takes a raw pointer. The important part is that
      // base::Bind() was passed a scoped_refptr; using a scoped_refptr
      // here would result in an extra AddRef()/Release() pair.
      void DoSomethingOnAnotherThread(SomeParamObject* param) {
    ...
    }
    };

      如果你想传递一个对象但是不想有任何引用相关的东西,可以用base::Unretained包装参数,再次提醒,这样意味着外部要保证这个对象的生命期,所以请小心使用。
      
      如果你的对象有一个特殊的析构函数需要运行在指定线程,那么你可以使用下面的特性。这样是必须的,因为时间竞争的关系,可能导致,发送任务的代码在异常做栈展开之前,任务会完整执行(这可能导致异常无法得到及时处理就因为已经发送出去的任务还在执行就崩溃了,我是这样理解的不知道对不对)。(This is needed since timing races could lead to a task completing execution before the code that posted it has unwound the stack.)
    class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::DeleteOnIOThread> {

    Callback cancellation

    取消任务有两个主要的原因(在回调的形式中):
    • 你会像稍后做些什么事情,但是现在回调再运行,你的对象可能已经删除了。
    • 当输入变更话(例如用户输入),旧的任务变得不再必须,为了表现好些,你需要取消掉它们。

    请看下面的几个不同的取消任务的方法

    Important notes about cancellation

      使用owned parameters(其实就是非引用计数来管理对象生存期的参数).的时候取消任务是很危险的,下面是个例子(例子中使用base::WeakPtr来做取消,但是问题是所有方法中都有的)

    class MyClass {
     public:
      // Owns |p|.
      void DoSomething(AnotherClass* p) {
        ...
      }
      WeakPtr<MyClass> AsWeakPtr() {
        return weak_factory_.GetWeakPtr();
      }
     private:
      base::WeakPtrFactory<MyObject> weak_factory_;
    };
    
    ...
    Closure cancelable_closure = Bind(object->AsWeakPtr(), p);
    Callback<void(AnotherClass*)> cancelable_callback = Bind(object->AsWeakPtr());
    ...
    
    void FunctionRunLater(const Closure& cancelable_closure,
                          const Callback<void(AnotherClass*)>& cancelable_callback) {
      ...
      // Leak memory!
      cancelable_closure.Run();
      cancelable_callback.Run(p);
    }
    在 FunctionRunLater中, 调用 Run()的时候如果对象已经西沟会导致内存泄露使用 scoped_ptr 可以解决这个问题.
    class MyClass {
    public: void DoSomething(scoped_ptr<AnotherClass> p) { ... } ... };


    base::WeakPtr and Cancellation[NOT THREAD SAFE]

      你可以使用base::WeakPtr和base::WeakPtrFactory (inbase/memory/weak_ptr.h)来去报任何调用都发生在对象的生存期内,而不使用引用计数。base::Bind的机制会在base::WeakPtr无效的时候禁止任务的执行。 base::WeakPtrFactory对象可以用来创建base::WeakPtr实例,这些实例知道base::WeakPtrFactory对象,当factory被删除的时候所有它创建WeakPtr会在内部设置为失效,这样就使得所有绑定的相关任务都不会派发执行。把factory对象设置成要派发任务的对象的成员,就会自动取消了。

      注意:这只发生在把任务发送到本线程的情况。现在有另外一种解决方案可以解决发送任务到其他线程的情况,参阅下一节CancelableTaskTracker。

    class MyObject {
    public:
    MyObject() : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
    }

    void DoSomething() {
    const int kDelayMS = 100;
    MessageLoop::current()->PostDelayedTask(FROM_HERE,
    base::Bind(&MyObject::DoSomethingLater, weak_factory_.GetWeakPtr()),
    kDelayMS);
    }

    void DoSomethingLater() {
    ...
    }

    private:
    base::WeakPtrFactory<MyObject> weak_factory_;
    };


    CancelableTaskTracker

      虽然base::WeakPtr对于取消任务很有用,但是它并不是线程安全的,所以不能用于在其它线程运行的任务。有时侯是些特殊的要求,例如,我们需要在用户输入发生改变的时候取消掉之前的数据库查询任务。这种情况下CancelableTaskTracker更合适。
      
      你可以用CancelableTaskTracker取消单个任务通过任务ID,这个也是为啥即使在同一线程也要用它代替base::WeakPtr的原因。
      CancelableTaskTracker与base::TaskRunner类似有两个方法做相同post task的事情,只是多了些支持取消任务的支持。
    class UserInputHandler : public base::RefCountedThreadSafe<UserInputHandler> {
      // Runs on UI thread.
      void OnUserInput(Input input) {
        CancelPreviousTask();
        DBResult* result = new DBResult();
        task_id_ = tracker_->PostTaskAndReply(
            BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB),
            FROM_HERE,
            base::Bind(&LookupHistoryOnDBThread, this, input, result),
            base::Bind(&ShowHistoryOnUIThread, this, base::Owned(result)));
      }
    void CancelPreviousTask() { tracker_->TryCancel(task_id_); }
    ... private: CancelableTaskTracker tracker_; // Cancels all pending tasks while destruction. CancelableTaskTracker::TaskId task_id_; ... };
    因为任务在其他线程运行,无法确保一定会成功取消。当TryCancel()调用的时候:
    • 如果任务和回复都没有运行,他们都将被取消
    • 如果过任务已经运行或者已经完成,恢复将被取消
    • 如果回复已经运行或者已经完成,取消就只是个空操作

     base::WeakPtrFactory一样CancelableTaskTracker也是在析构的时候取消所有相关的任务。

    Cancelable request(DEPRECATED)

    因为这个以后就废弃了就不翻译了。。。。

  • 相关阅读:
    【BZOJ】1671: [Usaco2005 Dec]Knights of Ni 骑士(bfs)
    【BZOJ】1689: [Usaco2005 Open] Muddy roads 泥泞的路(贪心)
    Maven使用
    上传图片
    Model、ModelMap和ModelAndView的使用详解
    Spring MVC 基于AnnotationFormatterFactory接口实现自定义的规则
    mybatis 分页插件PageHelper的简单使用
    Mybatis 自动生成mapper文件
    Tomcat部署时war和war exploded区别以及平时踩得坑
    springMVC @response 中文乱码解决
  • 原文地址:https://www.cnblogs.com/dwjaissk/p/2858593.html
Copyright © 2011-2022 走看看