我的Go并发之旅、01 并发哲学与并发原语
注:本文所有函数名为中文名,并不符合代码规范,仅供读者理解参考。
上下文
上下文(Context)代表了程序(也可以是进程,操作系统,机器)运行时的环境和状态,联系程序整个生命周期与资源调用,是程序可以访问到的所有资源的总和,资源可以是一个变量,也可以是一个对象的引用。
上下文切换
所谓的上下文切换(context switch),指的是发生进程调度(进程切换)时,内核(kernel)要把当前进程的状态和数据保存起来以备以后使用,同时把之前保存的进程的相关状态调出来,这样新调度出来的进程才能运行。
原语
原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。
计算机是一门人造科学,因此真正意义上的“原语”(Primitive)是不存在的。操作系统层面上的“原语”(比如 write 之类的系统调用)对程序员来讲的确是不可分割的最小单位,但是这些系统调用本身还是用好几句汇编语句组成的(对于 Linux 来说是 C 语言)。可能有人要说到了机器代码这一级就不能再分了,但事实上一条机器指令也是由好几个组合逻辑信号构成的。同样的道理,控制信号也不过是无数电子在器件内部漂移的结果。
因此定义“原语”的前提是观察者所处的位置(上下文)。一旦规定了观察者的位置和观察的角度,比如就在操作系统的这层上,read,write,wait这些个系统调用自然就是最“原始”的词汇,这也是为什么“原语”会在操作系统中频繁出现的缘故。
上下文也可以被定义为一个操作被认为是原子性的界限。
竞争条件
两个或多个操作必须按正确的顺序执行,而程序并未保证这个顺序,就会发生竞争条件。
大多数情况下,竞争条件会出现在数据竞争中,一个并发操作尝试读取一个变量,而在某个不确定的时间,另一个并发操作视图写入同一个变量。因为开发人员总是用顺序性的思维来思考问题,他们总假设某行代码会先于另一行代码执行。
func 竞争条件() {
var data int
go func() {
data++
}()
if data == 0 {
fmt.Println("Value", data)
}
}
在这段代码中会出现三种可能性,可以根据 go程(goroutine)、if判断、打印输出执行的顺序判断。
- if、打印、go程 => Value 0
- if、go程、打印 => Value 1
- go程、if => 跳过打印,无输出
仅仅几行代码就给程序带来了巨大的不确定性。
有时,某些程序员会写出一种看似解决了问题的“方案”,就是添加 time.Sleep,但是这种方案并不可靠!通过显式休眠的方式只是在概率上增加了逻辑的正确性,但不会真正变成逻辑上的正确。休眠时间也会影响程序的运行效果!
原子性
某些东西被认为是原子的,或者具有原子性的时候,这意味着在它运行的环境中,它是不可分割或不可中断的。在你所定义的上下文中,原子的东西将被完整的运行,在这种情况下不会同时发生任何事情。
在考虑原子性时,经常第一件需要做的事就是定义上下文或范围,然后再考虑这些操作是否是原子性的。
当一个东西是原子的,说明它在并发环境中是安全的。而大多数语句不是原子的,更不用说函数、方法和程序了,所以为了构建逻辑正确的程序,需要我们做内存访问同步,使用一系列操作来强制保持原子性。
内存访问同步
临界区:程序中需要独占访问共享资源的部分。(可以理解成读写共享资源的代码段)
正如前面的例子,保护程序临界区的一个方法是在临界区之间内存访问做同步。具体做法是添加一个互斥锁。
func 竞争条件_互斥锁() {
var lock sync.Mutex
var data int
go func() {
lock.Lock()
data++
lock.Unlock()
}()
lock.Lock()
if data == 0 {
fmt.Println("Value", data)
} else {
fmt.Println("Value", data)
}
lock.Unlock()
}
如果你发现你的代码中有临界区,那就添加互斥锁,保证各个临界区对共享资源(data)的独占访问权,从而对内存的访问进行了同步。但这仅仅解决了数据竞争,没有解决竞争条件 !这种方式同步对内存的访问有性能上的问题。
死锁
所有并发进程彼此等待。
活锁
正在主动执行并发操作的程序,但无法向前推进程序状态。
饥饿
在任何情况下,并发进程都无法获得执行工作所需的所有资源
通常意味着有一个或多个贪婪的并发进程,不公平地阻止(贪婪地抢占锁,以完成整个工作循环(通常是不必要地扩大其持有共享锁上的临界区))一个或多个并发进程以尽可能有效地完成工作。
饥饿也可能产生于 CPU、内存、文件句柄、数据库连接,任何必须共享的资源都是有可能产生饥饿的原因。
找到同步访问内存的平衡点
同步访问内存代价是昂贵的,所以将锁扩展到临界区外是有利的,但是会产生前面的饥饿问题。
需要在粗粒度和细粒度同步之间找到一个平衡点。
一般经验是将内存访问同步限制在关键部分,不扩展到临界区外;直到同步成为性能问题,再扩展范围。
给并发函数注释
- 谁负责并发,是调用者负责,还是函数自己负责。
- 如何利用并发原语解决这个问题的。比如递归调用。
- 谁负责同步,调用者负责内存访问同步还是结构体内部处理。
函数可以采用纯函数式方法,尽可能消除同步的问题。
函数最好返回一个只读的channel,而不是传入参数的指针,这样更具有明确性。
并发与并行
并发属于代码,并行属于一个运行中的程序的属性。
并行的"同时"是同一时刻可以多个进程在运行(处于running),并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。
实际上,当程序中写下多进程或多线程代码时,这意味着的是并发而不是并行。并发是因为多进程/多线程都是需要去完成的任务,不并行是因为并行与否由操作系统的调度器决定,可能会让多个进程/线程被调度到同一个CPU核心上。只不过调度算法会尽量让不同进程/线程使用不同的CPU核心,所以在实际使用中几乎总是会并行,但却不能以100%的角度去保证会并行。也就是说,并行与否程序员无法控制,只能让操作系统决定。
CSP
通信顺序进程。一个进程的输出应该直接流向另一个进程的输入。
Go魅力
一般编程语言会把它们的抽象链结束在系统线程和内存访问同步的层级。
但是Go语言采取了不同的路线,使用goroutine和channel代替这些概念。goroutine把我们从必须按照并行的思考方式中解放出来,作为替代,他允许我们按照更为自然的等级对问题进行建模。Go语言的运行时自动地将goroutine映射到系统的线程上,并为我们管理它们之间的调度(智能分配OS线程)。
Go并发哲学
不要通过共享内存进行通信,通过通信来共享内存。在面对不同场景时,选择不同的方式。

数据所有权:并发程序安全就是保证同时只有一个并发上下文拥有数据的所有权,通过channel可以把数据传递给其他go程,解耦生产者和消费者。
追求简洁,尽量使用channel,并且认为goroutine的使用是没有成本的。
参考书籍
《Go语言并发之道》Katherine CoxBuday
《Go语言核心编程》李文塔
《Go语言高级编程》柴树彬、曹春辉
我的Go并发之旅、01 并发哲学与并发原语的更多相关文章
- Cocos2d-x 3.x游戏开发之旅
Cocos2d-x 3.x游戏开发之旅 钟迪龙 著 ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...
- Android开发之旅: Intents和Intent Filters(理论部分)
引言 大部分移动设备平台上的应用程序都运行在他们自己的沙盒中.他们彼此之间互相隔离,并且严格限制应用程序与硬件和原始组件之间的交互. 我们知道交流是多么的重要,作为一个孤岛没有交流的东西,一定毫无意义 ...
- ArcGIS Engine开发之旅10--空间参考及坐标转换
原文:ArcGIS Engine开发之旅10--空间参考及坐标转换 空间参考(Spatial Reference)是 GIS 数据的骨骼框架,能够将我们的数据定位到相应的位置,为地图中的每一点提供准确 ...
- ArcGIS Engine开发之旅09--几何对象和空间参考
原文:ArcGIS Engine开发之旅09--几何对象和空间参考 1.Geometry Geometry 是 GIS 中使用最为广泛的对象集之一,用户在创建.删除.编辑和进行地理分析的时候,就是处 ...
- ArcGIS Engine开发之旅08--和查询相关的对象和接口
原文:ArcGIS Engine开发之旅08--和查询相关的对象和接口 查询在GIS领域应该是一个很频繁的操作,在GIS中除了具有属性查询(和其他关系型数据库的查询类似),还提供了空间查询.在介绍查询 ...
- ArcGIS Engine开发之旅07---文件地理数据库、个人地理数据库和 ArcSDE 地理数据库中的栅格存储加以比较 、打开栅格数据
原文:ArcGIS Engine开发之旅07---文件地理数据库.个人地理数据库和 ArcSDE 地理数据库中的栅格存储加以比较 .打开栅格数据 对文件地理数据库.个人地理数据库和 ArcSDE 地理 ...
- ArcGIS Engine开发之旅05---空间数据库
原文:ArcGIS Engine开发之旅05---空间数据库 1 Geodatabase概念 Geodatabase是ArcInfo8引入的一种全新的面向对象的空间数据模型,是建立在DBMS之上的统 ...
- ArcGIS Engine开发之旅04---ARCGIS接口详细说明
原文:ArcGIS Engine开发之旅04---ARCGIS接口详细说明 ArcGIS接口详细说明... 1 1. IField接口(esriGeoDatabase)... 2 2. ...
- ArcGIS Engine开发之旅03--ArcGIS Engine中的控件
原文:ArcGIS Engine开发之旅03--ArcGIS Engine中的控件 制图控件,如MapControl.PageLayoutControl,其中MapControl控件主要用于地理数据的 ...
随机推荐
- colab运行.py文件
!python split_data.py
- 高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
简介 这是一个使用Java(以后还会推出Kotlin版本)语言,从0开发一个Android平台,接近企业级的项目(我的云音乐),包含了基础内容,高级内容,项目封装,项目重构等知识:主要是使用系统功能, ...
- 什么是pytorch?
Pytorch是基于python的科学计算包,为两类受众提供服务 作为Numpy的替换,让你可以使用GPU的算力 作为一个深度学习计算平台提供最大的计算灵活性与速度 开始体验pytorch的基础功能 ...
- .NET Core 实现后台任务(定时任务)IHostedService(一)
原文链接:https://www.cnblogs.com/ysmc/p/16456787.html 最近有小伙伴问道,在 .Net Core 中,如何定时执行任务,而因为需要执行的任务比较简单,并不想 ...
- Identity Server 4客户端认证控制访问API
项目源码: 链接:https://pan.baidu.com/s/1H3Y0ct8xgfVkgq4XsniqFA 提取码:nzl3 一.说明 我们将定义一个api和要访问它的客户端,客户端将在iden ...
- Mac Sierra开启读写NTFS
查看卷标diskutil list sudo vim /etc/fstab,开启WinD盘符读写,添加如下: LABEL=WinD none ntfs rw,auto,nobrowse 磁盘工具重新挂 ...
- 牛客SQL刷题第三趴——SQL必知必会
01检索数据 SQL60 从 Customers 表中检索所有的 ID 编写 SQL 语句,从 Customers 表中检索所有的cust_id select * from Customers; SQ ...
- Java 技术栈中间件优雅停机方案设计与实现全景图
欢迎关注公众号:bin的技术小屋,阅读公众号原文 本系列 Netty 源码解析文章基于 4.1.56.Final 版本 本文概要 在上篇文章 我为 Netty 贡献源码 | 且看 Netty 如何应对 ...
- Linux 启动流程及相关知识
基础知识 linux系统的组成 内核(kerner) 根文件系统(rootfs) 内核提供操作系统的功能,根文件系统包含常用的一些工具,这些工具.这些工具的运行离不开glibc库文件. 程序:二进制程 ...
- 从零开始Blazor Server(2)--整合数据库
开篇 上一篇文章我们留了个尾巴,没有把freesql整合进去,这篇文章我们来整合. 目前的思路呢,是做一个简单的四不像的RABC,也有用户.角色. 权限三部分. 但是其中每个用户只有一个角色,即用户和 ...