引言

作者:程序员bingo,主要关注客户端架构设计、性能优化、崩溃处理,有多年的Chromium浏览器开发经验。

多线程一直是软件开发中最容易出问题的环节,很多的崩溃、卡死问题都与多线程有关。在常用的线程模型中,一般会使用线程锁保证线程数据安全,但是,在实践中,这种模式很容易造成漏加锁、锁粒度太大、死锁等问题。

要解决这类问题,一种比较好的方式是采用无锁的线程模型,Chromium就是采用了这种线程模型。本文通过Electron基于Chromium线程模型实现的打开文件对话框功能,介绍无锁线程模型的思路。

无锁线程模型简介

应用层数据不加锁

chromium的无锁线程模型,不是指完全的不使用线程锁,因为底层的Task队列是有加锁的,而是指在应用层使用时,不需要添加线程锁。

不同线程不会同时访问数据

无锁线程模型,主要是保证在同一时间,不同线程在同一时间不会同时访问相同的数据。下面的例子要用到的方法,主要是对不同线程访问数据的能力进行隔离。对数据访问能力隔离方式主要有

  • 拷贝,在不同线程传递数据时,对数据进行一份拷贝,让两个线程访问的是不同数据。
  • 移动,在不同线程传递数据时,使用std::move进行右值转移,让原线程无法访问这个数据。

无锁线程模型示例

下面以Electron的dialog.showOpenDialog实现代码为例,说明Chromium的无锁线程模型的使用原理。

dialog.showOpenDialog的调用

Electron的接口函数dialog.showOpenDialog是一个异步的JavaScript函数,会返回一个promise。showOpenDialog会弹出一个文件选择对话框,用户选择文件之后,把文件路径通过result.filePaths返回。

dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
}).then(result => {
console.log(result.canceled)
console.log(result.filePaths)
}).catch(err => {
console.log(err)
})

Chromium线程相关的几个基本概念

  • TaskRunner:每一个线程有一个TaskRunner,主要通过PostTask把任务投放到线程的任务队列,通过线程安全的引用技术管理生命周期,可配合scoped_refptr在不同线程使用。
  • PostTask:TaskRunner的一个函数,可向该线程的任务队列中发送一个闭包,闭包会在该线程中执行。

相关代码如下:

class BASE_EXPORT TaskRunner
: public RefCountedThreadSafe<TaskRunner, TaskRunnerTraits> {
public:
bool PostTask(const Location& from_here, OnceClosure task);
}

C++代码响应JavaScript调用

在JavaScript调用dialog.showOpenDialog之后,会在UI线程中,调用到C++代码的ShowOpenDialog函数。ShowOpenDialog函数主要是创建一个新的Dialog线程,然后通过PostTask把RunOpenDialogInNewThread函数抛到这个Dialog线程去运行。这个过程中,需要处理所有权的参数有3个,run_state、settings和promise。

  • run_state、settings是通过拷贝方式隔离不同线程的访问权。run_state除了保存有Dialog线程的指针外,还有UI线程的TaskRunner,用于后续Dialog线程往UI线程发送回调函数。
  • promise是通过std::move进行所有权转移,转移之后,就只有Dialog线程的函数RunOpenDialogInNewThread有权访问,UI线程暂时无权限访问。

相关代码如下:

struct RunState {
base::Thread* dialog_thread;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner;
}; bool CreateDialogThread(RunState* run_state) {
auto thread =
std::make_unique<base::Thread>(ELECTRON_PRODUCT_NAME "FileDialogThread");
thread->init_com_with_mta(false);
if (!thread->Start())
return false; run_state->dialog_thread = thread.release();
run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
return true;
} void ShowOpenDialog(const DialogSettings& settings,
gin_helper::Promise<gin_helper::Dictionary> promise) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
RunState run_state;
if (!CreateDialogThread(&run_state)) {
dict.Set("canceled", true);
dict.Set("filePaths", std::vector<base::FilePath>());
promise.Resolve(dict);
} else {
run_state.dialog_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&RunOpenDialogInNewThread, run_state,
settings, std::move(promise)));
}
}

Dialog线程执行任务,并把结果返回给UI线程

RunOpenDialogInNewThread函数会在Dialog线程中运行,它通过ShowOpenDialogSync函数获取到选中的文件路径paths,并通过拷贝的方式,返回结果result和paths。

这时,promise的所有权再次通过std::move进行了转移,转移之后,只有UI线程的OnDialogOpened函数有权访问。

相关代码如下:

void RunOpenDialogInNewThread(
const RunState& run_state,
const DialogSettings& settings,
gin_helper::Promise<gin_helper::Dictionary> promise) {
std::vector<base::FilePath> paths;
bool result = ShowOpenDialogSync(settings, &paths);
run_state.ui_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&OnDialogOpened, std::move(promise), !result, paths));
run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
}

UI线程处理返回结果

此时,回到了UI线程,OnDialogOpened函数对promise进行处理后,返回给JavaScript的promise的处理结果,最终会回到JavaScript的promise的then函数。

相关代码如下:

void OnDialogOpened(gin_helper::Promise<gin_helper::Dictionary> promise,
bool canceled,
std::vector<base::FilePath> paths) {
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
dict.Set("canceled", canceled);
dict.Set("filePaths", paths);
promise.Resolve(dict);
}

处理流程

promise的可访问权,是跟着showOpenDialog处理顺序进行变化,执行属性如下:

  • 在UI线程运行函数:ShowOpenDialog
  • 在Dialog线程运行函数:RunOpenDialogInNewThread
  • 在UI线程运行函数:OnDialogOpened

相关流程图如下:

小结

上面通过Electron的dialog.showOpenDailog,介绍了Chromium的无锁线程模型的一些使用思路。这个例子是通过拷贝和移动语义来保证不同线程无法同时对同一变量进行访问,从而不需要加锁。如果能够正确使用这种线程模型,是可以消除因为数据锁带来的一些线程同步问题。

关于作者

微信公众号:程序员bingo

Blog: https://bingoli.github.io/
GitHub: https://github.com/bingoli

Chromium的无锁线程模型C++代码示例的更多相关文章

  1. .NET 固定时间窗口算法实现(无锁线程安全)

    一.前言 最近有一个生成 APM TraceId 的需求,公司的APM系统的 TraceId 的格式为:APM AgentId+毫秒级时间戳+自增数字,根据此规则生成的 Id 可以保证全局唯一(有 N ...

  2. winsock编程IOCP模型实现代码

    winsock编程IOCP模型实现代码 话不多说,上代码.借鉴<windows核心编程>部分源码和CSDN小猪部分代码. stdafx.h依赖头文件: #include <iostr ...

  3. 服务端线程模型-NIO服务模型

    上接<服务端线程模型-线程池服务模型>(https://www.cnblogs.com/fudashi233/p/10549221.html). 这篇分享从最初的单线程服务模型一直演进到线 ...

  4. EF自动生成的模型edmx代码分析

    edmx代码分析 本文分析Entity Framework从数据库自动生成的模型文件代码(扩展名为edmx). 1. 概述 本文使用的数据库结构尽量简单,只有2个表,一个用户表和一个分公司表(相当于部 ...

  5. Chromium与CEF的多进程模型及相关參数

    CEF基于Chromium,也是多进程模型.关于进程模型.參考这里:https://www.chromium.org/developers/design-documents/process-model ...

  6. 【转】- 从FM推演各深度CTR预估模型(附代码)

    从FM推演各深度CTR预估模型(附代码) 2018年07月13日 15:04:34 阅读数:584 作者: 龙心尘 && 寒小阳 时间:2018年7月 出处: 龙心尘 寒小阳

  7. Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测

    Kaggle网站流量预测任务第一名解决方案:从模型到代码详解时序预测 2017年12月13日 17:39:11 机器之心V 阅读数:5931   近日,Artur Suilin 等人发布了 Kaggl ...

  8. 漫谈四种神经网络序列解码模型【附示例代码】 glimpse attention

    漫谈四种神经网络序列解码模型[附示例代码] http://jacoxu.com/encoder_decoder/ [视觉注意力的循环神经网络模型]http://blog.csdn.net/leo_xu ...

  9. PHP+Ajax+plupload无刷新上传头像代码

    很简单的一款PHP+Ajax+plupload无刷新上传头像代码,兼容性很好,可以直接拿来用.你可以自定义各种类型的文件.本实例中只能上传"jpg", "png" ...

随机推荐

  1. Laravel5.4 队列简单配置与使用

    概述 什么是队列? 百度百科是这样说的 “队列”是在传输过程中保存数据的容器. 举几个生活中例子: * iphone手机新款发布,三里屯iphone进的新货.大家要排队买,不能说一大堆人一起冲进去,那 ...

  2. numpy.ravel() 与 numpy.flatten()

    两者都可实现将多维数组降位一维的功能 numpy.flatten()返回拷贝,对拷贝所做的修改不会影响原始矩阵 numpy.ravel()返回视图,会影响原始矩阵 1)ravel() In [16]: ...

  3. python语法基础-基础-变量和数据类型

    ###############   第一个python程序   ############### print("hello python") # 打印hello python # 分 ...

  4. Zookeeper开源客户端框架Curator的使用

    CuratorFramework Curator框架提供了一套高级的API, 简化了ZooKeeper的操作. 话不多说,看代码 package com.donews.data.util import ...

  5. PEAKS|NovoHMM|Nover|DeepNovo|MAYUPercolator|UniprotKB|Swiss-prot|Mascot|SEQUEST|X!Tandem|pFind|MaxQuant|Msconvert|PEPMASS|LC|

    质谱仪: 质谱分析法是先将大分子电离为带电粒子,按质核比分离,由质谱仪识别电信号得到质谱图. Top-down直接得到结果是蛋白. Bottom down使用shutgun方法得到结果是肽段. 由蛋白 ...

  6. mysql表关联问题(第三卷:外键多对多)

    现在我们整理一下多对多的问题,举个例子现在一个男的可能和多个女的谈过恋爱,一个女的也可能和多个男的谈过恋爱,把他们恋爱的关系整理为数据关联表就成为了多对多的关系. 准备三张表,男人信息表,女人信息表, ...

  7. GitHub下载

  8. html和jsp页面中把文本框禁用,只能读不能写的方法

    方法常用有三种: 第一种,使用   onfocus="this.blur()" <input name="deptno" type="text& ...

  9. 20181026_队测_Brick Game

    题目描述 给出一个\(n\)行\(m\)列的矩阵,矩阵中每个格子有一个非负整数,现在要求你去除其中的个格子,使得剩下的格子中的数的总和最大.另外,去除\(k\)个格子后,剩下的格子必须满足以下几个性质 ...

  10. 蚂蚁金服招聘-无线测试开发(20k-36k/月)

    蚂蚁金服-支付宝国际事业部-高级测试开发工程师/测试专家 工作年限:三年以上学历要求:本科期望层级:P6/P7工作地点:上海,杭州,深圳等为什么选择加入我们? 我们的岗位有何不同?1.国际化远景:随着 ...