chromium学习:thread

        在前面,我们学习了content_shell的流程,串成了一条执行线。从今天开始,我们要开始学习线上的点,由点延伸成为面,最后组成一个学习chromium的网。

        

        首先,我们先介绍chromium中的thread相关知识。

         

          chromium中有哪些线程呢?

          1. UI线程。应用程序起来后的主线程。

          2. IO线程。负责browser进程和子进程之间的调度线程

          3. file线程。不解释

          4. db线程。不解释

          5. safe_browsing线程。不清楚


         chromium关于thread的设计原则有两个:1. 不阻塞UI线程,使得UI有更好的响应。2.不鼓励加锁机制和线程安全对象。

        

         如何做到的呢?那就要考虑chromium中的线程模型了。


         1. 不在UI线程作阻塞IO操作,不在IO线程作阻塞IO操作。


         2. 线程之间不互相阻塞


         3. 许多API都是异步的


         为了避免加锁机制,chromium提供的thread模型是在每个线程内保留消息循环,线程之间通过消息传递任务,处理回调函数。

       

        关于多线程的加锁,摘抄如下一段话

多线程编程一直是一件麻烦的事情,线程执行的不确定性,资源的并发访问,一直困扰着众多程序员们。为了解决多线程编程的麻烦,大家想出了很多经典的方案:如:对资源直接加锁,角色模型,CSP,FP等等。他们的思想基本分为两类:一类是对存在并发访问资源直接加锁,第二类是避免资源被并发访问。前者存在许多问题,如死锁,优先级反转等等,而相对来说,后者会好很多,角色模型,CSP和FP就都属于后者,Chrome也是使用后者的思想来实现多线程的。


         从开发的角度,我们该如何使用chromium提供的这种线程模型呢?

         

         提供模板base::Callback<>,该类有一个Run函数。其作为函数指针泛型,由base::Bind产生。废话少说,看个例子就什么都明白了


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)>。参数部分请参考 柯里化 


        如何往一个线程里面放任务呢?


        chromium提供的线程模型里面提供PostTask,PostDelayedTask方法。我们以PostTask为例。

void MessageLoop::PostTask(
    const tracked_objects::Location& from_here, const base::Closure& task)
其中,
typedef Callback<void(void)> Closure;

        因此,可以看到如下一个简单的例子

void WriteToFile(const std::string& filename, const std::string& data);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                        base::Bind(&WriteToFile, "foo.txt", "hello world!"));

        如何让一个类成员函数作为task呢?


        同样的道理,只是第一个参数变为类对象。如下例子,不过注意对象通常是引用计数的。

class MyObject : public base::RefCountedThreadSafe<MyObject> {
 public:
  void DoSomething(const std::string16& name) {
    thread_->message_loop()->PostTask(
       FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, <strong>this</strong>, 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::bind中的参数会被复制到内部存储结构。

     取消回调

有两种情形需要取消回调:

1. 需要在一个对象稍后做一些事情,但是到那时,当你的回调调用的时候,对象可能被删除了

2. 情形改变了,有些老任务为了效率考虑需要取消。

    取消的重要声明:

    取消一个拥有参数的task是危险的,因为可能造成内存泄漏。参考下例

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(&MyClass::DoSomething, object->AsWeakPtr(), p);
Callback<void(AnotherClass*)> cancelable_callback = Bind(&MyClass::DoSomething, object->AsWeakPtr());
...

void FunctionRunLater(const Closure& cancelable_closure,
                      const Callback<void(AnotherClass*)>& cancelable_callback) {
  ...
  // Leak memory!
  cancelable_closure.Run();
  cancelable_callback.Run(p);
}

    

    在FunctionRunLater中,如果object已经被释放的话,两个Run()函数都会造成p泄漏。我们可以使用scoped_ptr来解决这个问题

class MyClass {
 public:
  void DoSomething(scoped_ptr<AnotherClass> p) {
    ...
  }
  ...
};

     base::WeakPtr 和取消

    我们可以使用base::WeakPtr 和 base::WeakPtrFactory,用于保证在object的生命周期之外对应的调用不会发生,甚至不必使用引用计数。base::Bind为base::WeakPtr提供了一种机制,当base::WeakPtr设置为无效的时候,禁止task的执行。其中base::WeakPtrFactory是用于生成base::WeakPtr对象的。当factory释放的时候,所有base::WeakPtr都会在其内部设置“无效”位,用于任何绑定于他们的task不会被执行。在需要分发消息的对象里面存储一个factory成员变量,在对象析构的时候可以做到task自动取消。

    但是请注意:这只是在task被发往同一个线程时候才有效。如今还没有比较通用的方法用于多线程之间的任务,参考下一部分关于CancelableTaskTracker的说明。

     例子如下

class MyObject {
 public:
  MyObject() : 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_;
};

    可取消的任务

    虽然base::WeakPtr很有用,但是他不是线程安全的,不能用于跨线程的任务。这种情况CancelableTaskTracker更为合适。

    使用CancelableTaskTracker你可以使用任务返回的id来取消另一个线程的任务。这同样可以被应用在同一个线程。

    CancelableTaskTracker相比base::TaskRunner,只是增加了任务的取消功能。

    例子如下。

   

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_;
  ...
};

    任务由于是在另一个线程执行,不能保证一定会被取消。但是可以保证不会crash吧

    当我们使用TryCancel()时候,

       1. 如果task和reply都没有开始执行,则都会取消

       2. 如果task正在执行或者已经执行完,reply会取消

       3. 如果reply正在执行或者已经执行完,无操作

    就像base::WeakPtrFactory ,CancelableTaskTracker  在析构的时候将会取消所有的task


     chromium提供的线程模型确实是很高明的,没有锁的情况下提供了多种解决方案。


     以下的参考很不错。第一个是我本文的主要来源,或者说翻译了部分精华内容。第二个从源码的角度分析了thread。第三个就从实现的角度讲解。


参考:


1. http://www.chromium.org/developers/design-documents/threading


2. http://0xffff.me/2011/12/30/%E9%97%B2%E8%AF%9Dchromium%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.html


3. http://bigasp.com/archives/478

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值