golang http 最大请求数_Golang 并发问题(四)之单核上的并发问题 - Go语言中文社区

golang http 最大请求数_Golang 并发问题(四)之单核上的并发问题


Go语言中文网,致力于每日分享编码知识,欢迎关注我,会有意想不到的收获!

f0bcf519eafb7a3ee697877647ed6632.png

01

写在前面

过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索和总结。这是一个系列文章,本文为第四篇。

本文简单介绍 Golang 中配置可用 CPU 核的方法及其可能导致的误解。

02

Golang 在单核上的“并发”问题

c83199a8d3bc4618a5c1219ba331d8a1.png

gotour上的乌龙案例

在浅谈 Golang 中数据的并发同步问题(三)—Map的并发问题中介绍了 Golang 并发编程中 map 类型的“脆弱”性。具体地,Golang 的运行时(runtime)会强校验并发读写的状态,如果发现有协程(goroutine)读 map 同时有其他协程读或者写同一个 map,程序就会直接异常退出。

然而蹊跷的是,在 Golang 官方教程中,并发部分有一个示例(见这里 http://tour.studygolang.com/concurrency/9)却并没有因为多个协程并发写同一个 map 变量而异常退出。示例的主要内容是通过一个 Mutex 锁来限定 SafeCounter 结构体中的 v 变量(map类型)的并发读写,其源码如下:

fd2e2614a1cda25965b3ef292c4d0c00.png

如果去掉 Inc 函数中 mux 加锁与解锁的过程(如下面的代码所示),理论上示例代码会报出 concurrent map writes 错误,但是如果登录官方对应的 tour 页面,修改 Inc 方法后运行却并未报出 并发写 map 的错误(此结论截止到 2019/05/15,已经提了 issue,官方可能会做修复)

// 修改后的 Inc 函数,此处去掉了锁相关的过程func (c *SafeCounter) Inc(key string) {c.v[key]++}

单个物理核心上的“并发”

如果 CPU 只有单个物理核,Golang 运行时(runtime)如何才能实现逻辑上的 “并发” 呢? 其实我们可以类比操作系统的多进程模型(参考《 Linux系统调度原理浅析 》和《 Linux系统调度原理浅析(二) 》),引入 时间片 的概念,把一个物理核的使用权按时间片划分并分配给所有的协程(goroutine),每个协程消耗自己的时间片 轮流交替 在同一个物理核上运行,从而实现逻辑上的 “并发”。

其实这里面就涉及到一个问题,如果 Golang 代码运行时只被分配了一个物理核(比如宿主机只有一个物理核,或者通过 runtime.GOMAXPROCS(1)显式配置 Golang 进程只能使用一个核),那么是否就意味着 Golang 运行时(runtime)对 map 的读写都变成了顺序的从而避免了并发错误呢?

目前来看,一个物理核的运行时配置确实会让 map 表现的不那么 “脆弱”。Golang 官方 http://tour.studygolang.com/concurrency/9 这个示例所运行的服务器很大概率默认添加了单个物理核的限制(可能考虑到节省资源),从而导致上面提到的乌龙示例。不过这里需要特别说明一下,按照进程调度的基本原理,假设每个协程可以在任意过程被中断,理论上单个物理核上也可能会引发 map 的并发错误从而导致进程异常退出(因为 map 的读与写过程都很复杂,二者都不是原子性的),从这个角度配置单个核并不能保证 Go 线程安全(此项有待进一步确认)。

上面所提到的乌龙示例一般不会碰到,因为大部分的开发环境都是多核心的;不过如果开发环境是单核配置的虚拟机就会遇到了(我周围就有朋友用单核的虚拟机作为开发环境学习 Golang)。

runtime.GOMAXPROCS(1) 方法

翻译官方对 GOMAXPROCS 的描述:GOMAXPROCS 可设置能够同时运行代码逻辑的最大 CPU 数量。

在了解了单个 CPU 核心 对 map 类型变量的影响后,可能有的同学会考虑通过 runtime.GOMAXPROCS(1) 限制 Golang 应用可使用的 CPU 核心从而增加代码的健壮性——其实这种考虑是比较危险的。

首先, map 的读写过程都不是原子性的(原子性的概念参考浅谈 Golang 中数据的并发同步问题(二)中的阐述),这就导致读写过程可能被在任意过程中断,从而引发 map 的并发读写校验生效导致程序异常退出(这一条有待进一步确认 goroutine 的调度机制)。其次,在低成本创建 goroutine 的编程模型中,单核心的配置可能造成逻辑死锁,比如下面的代码就会僵死:

1d82a5a0e4283658a4a1efde5c19568f.png

03

小结

f5649a12ad5a6903fc6db34103511f3a.png

Golang 运行时默认会启用所有的 CPU 核心,可以通过 runtime.GOMAXPROCS() 方法配置可用的最大 CPU核心数量。当只有一个 CPU 核的时候(比如虚拟机只配置了一个物理核,或者通过 runtime.GOMAXPROCS(1) 配置只使用一个物理核),会对 map 类型变量的并发稳定性产生一些影响(不加锁的情况下也不会出现并发读写问题),但是开发者不应该依赖这个特性来试图增加代码的健壮性,否则会造成无法预料的结果。

参考

  • Linux系统调度原理浅析 - 敬维 简单介绍了 进程、线程、多线程模型、时间片以及调度等概念 https://jingwei.link/2018/12/30/linux-process-thread-schedule.html
  • Linux系统调度原理浅析(二) - 敬维 简单介绍了 进程、线程、调度以及Goroutine的调度 https://jingwei.link/2019/02/13/linux-process-thread-schedule-2.html
  • 浅谈 Golang 中数据的并发同步问题(二)
  • golang多核设置 介绍了 golang 的多核配置方法(runtime.GOMAXPROCS(1))及示例 https://studygolang.com/articles/5025

著作权归作者所有。

原文: https://jingwei.link/2019/05/15/golang-concurrency-04-single-core.html

本文作者:敬维,原创授权发布

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_34409887/article/details/112072792
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-17 22:17:25
  • 阅读 ( 1535 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢