【协作式原创】查漏补缺之Go并发问题(单核多核)
主要回答一下几个问题
1.单核并发问题
2.多核并发问题
2.几个不正确的同步案例
1.单核并发问题
- 先看一段go(1.11)代码: 单核CPU,1万个携程,每个携程执行100次+1操作, 思考n最终会打印多少?
package main
import (
"fmt"
"time"
"runtime"
"sync"
)
var n int
var wg sync.WaitGroup
func main() {
runtime.GOMAXPROCS(1) //单核
// runtime.GOMAXPROCS(2) //多核
wg.Add(10000)
for i:=0;i<10000;i++{
go add()
}
wg.Wait()
fmt.Println("累加结果:",n)
}
func add() {
for i := 0; i < 100; i++ {
n++
time.Sleep(1)
}
wg.Done()
}
//output 单核
累加结果: 1000000
//output 多核
累加结果: 970820
- 对比一段c语言多线程代码(单核运行),思考TestInteger会打印多少
// 编译: gcc main.c -o main -plthread
// 运行: ./main.exe
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 重定义数据类型
typedef signed int INT32;
typedef unsigned int UINT32;
// 宏定义
#define THREAD_NUM 2 // 线程个数
UINT32 g_iTestInteger = 0;
// 函数声明
void ProcessTask(void *pParam);
int main(void) {
pthread_t MultiHandle = 0; // 多线程句柄
UINT32 iLoopFlag = 0;
INT32 iRetVal = 0; // 创建线程函数的返回值
// 循环创建线程
for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++)
{
iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ProcessTask), (void *)iLoopFlag);
if (0 != iRetVal)
{
printf("Create ProcessTask %d failed!\n", iLoopFlag);
return -1;
}
}
Sleep(2000); /* windows 使用Sleep,参数为毫秒 */
printf("In main, TestInteger = %d\n", g_iTestInteger);
return 0;
}
void ProcessTask(void *pParam){
for (int i = 0;i<100;i++){
g_iTestInteger ++;
Sleep(1); /* windows 使用Sleep,参数为毫秒 */
}
}
//output
In main, TestInteger = 198
Q: 单核环境下,对于n++问题,go为什么没有并发问题,而c语言有并发问题?
A:
n++对应的汇编指令是3条.
1.1 加载: 加载n到寄存器,
1.2 更新: 更新寄存器(n+1)
1.3 存储(写回内存): 把寄存器的值存储到内存中n对应的内存地址中
参考<深入理解计算机系统第3版>12.3小节的图12-18b:
c语言的多线程调度是抢占式的,多线程的上下文切换可以发生在任何指令之间(TODO除了少数原子指令)。
所以c语言是有并发问题的。go的非抢占式调度携程, 上述代码只在函数调用时触发协程切换(go1.14版本以前,调用sleep时触发调用), 所以n++的3个指令可以一次执行完成,然后进入sleep才切换到另一个go携程,所以每个携程的n++是串行执行的,即使用1万个携程来测试也没有并发问题:
0++
sleep切换
1++
sleep切换
打印2,退出
总结:go在sleep时才发生协程切换,c语言的多线程切换可能发生在任何指令处,两者的切换粒度不一样。
TODO:go1.11具体是怎么一个非抢占式度。
Q: 为什么GO代码单核没有问题,多核有问题
参考附录1
- 在单核CUP的情况下,每个CPU只会运行一个go携程,并且每个go携程执行时不会中断n++的指令,所以至少这些携程的n++语句是串行执行的,所以不会有并发问题。
- 在多核的情况下,多个CPU可以同时运行多个go携程,即使n++不被中断,但是由于多个携程同时读取相同的内存值,会出现后提交覆盖先提交的情况,所以会导致并发问题。
您说的没错,我写了一段新的代码 https://play.studygolang.com/p/NkQhyGaMtnF
1. 这段代码在playground上是没有并发问题的。是因为playgound的执行环境是单核CPU,由于go携程是非抢占式调度的,所以每个时刻其实只有一个携程在执行CompareAndSwapInt64进行+1操作,所以是不会有并发冲突的。
2. 这段代码在本机的多核环境下运行是有并发问题的。因为每个时刻有多个CPU在同时执行多个go携程,那么就会有多个携程同时读到同一个G_Int的情况,在go携程把更新后的值写回内存时,就会发生Compare失败的情况。
Q: 多核为什么有并发问题?
A:
对于go
尽管携程是非抢占式调度的,但是如果有多核的话,就有多个P来同时执行携程。TODO
对于c
- 同时多个cpu读取到了相同的n,后提交的线程会把先提交的线程的n++结果覆盖掉,导致部分线程加1操作丢失。
Q: 加锁时如何解决c语言的多核多线程并发问题
A:
- 锁的两种底层原理
总线锁:
缓存锁:
TODO
- 什么是原子操作
使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。
https://mp.weixin.qq.com/s?src=11×tamp=1581489449&ver=2153&signature=7Iz6mUVmEfBAV7NycB4e3YlB9eYGFMIcAOQfKOvIxF3yxqinxRUrAV5qDcw1wIQlpePi6AFDeQbTAOMWsWed2rSt0UW3F9fR*B0sVSh8Bunqdt5IWCCiNDBMsoT7iIl5&new=1 - 原子操作(1) - 用汇编实现原子操作
https://www.cnblogs.com/drunkard87/articles/3759159.html - 原子操作的pater : 关键字直接搜 linux kernel memory barrier
- go官方原子操作库
https://golang.org/pkg/sync/atomic/ - 1.单核CPU的原子操作: 在单核CPU中,
能够在一个指令中完成的操作
都可以看作为原子操作, 因为中断只发生在指令间
.
单核/多核CPU的原子操作的含义
加锁的2种底层实现
然后加锁操作的话,对应图中就是对cpu总线加锁,使得同一时刻只有一个cpu能访问内存。但是这个效率比较低,于是有了基于cpu缓存的锁。
加锁的2种底层实现,我在这看的:https://mp.weixin.qq.com/s/RDEQSOjrSBVYVq6LV5MslQ
Q: 问:如何实现x++的原子性?
在单处理器上,如果执行x++时,禁止多线程调度,就可以实现原子。因为单处理的多线程并发是伪并发。
在多处理器上,需要借助cpu提供的Lock功能。锁总线。读取内存值,修改,写回内存三步期间
禁止别的CPU访问总线。同时我估计使用Lock指令锁总线的时候,OS也不会把当前线程调度走了。要是调走了,那就麻烦了。
CPU中的原子操作
参考资料
【协作式原创】查漏补缺之Go并发问题(单核多核)的更多相关文章
- Java查漏补缺(3)(面向对象相关)
Java查漏补缺(3) 继承·抽象类·接口·静态·权限 相关 this与super关键字 this的作用: 调用成员变量(可以用来区分局部变量和成员变量) 调用本类其他成员方法 调用构造方法(需要在方 ...
- Java基础查漏补缺(2)
Java基础查漏补缺(2) apache和spring都提供了BeanUtils的深度拷贝工具包 +=具有隐形的强制转换 object类的equals()方法容易抛出空指针异常 String a=nu ...
- CSS基础面试题,快来查漏补缺
本文大部分问题来源:50道CSS基础面试题(附答案),外加一些面经. 我对问题进行了分类整理,并给了自己的回答.大部分知识点都有专题链接(来源于本博客相关文章),用于自己前端CSS部分的查漏补缺.虽作 ...
- Go语言知识查漏补缺|基本数据类型
前言 学习Go半年之后,我决定重新开始阅读<The Go Programing Language>,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书 ...
- 《CSS权威指南》基础复习+查漏补缺
前几天被朋友问到几个CSS问题,讲道理么,接触CSS是从大一开始的,也算有3年半了,总是觉得自己对css算是熟悉的了.然而还是被几个问题弄的"一脸懵逼"... 然后又是刚入职新公司 ...
- js基础查漏补缺(更新)
js基础查漏补缺: 1. NaN != NaN: 复制数组可以用slice: 数组的sort.reverse等方法都会改变自身: Map是一组键值对的结构,Set是key的集合: Array.Map. ...
- Entity Framework 查漏补缺 (一)
明确EF建立的数据库和对象之间的关系 EF也是一种ORM技术框架, 将对象模型和关系型数据库的数据结构对应起来,开发人员不在利用sql去操作数据相关结构和数据.以下是EF建立的数据库和对象之间关系 关 ...
- 2019Java查漏补缺(一)
看到一个总结的知识: 感觉很全面的知识梳理,自己在github上总结了计算机网络笔记就很累了,猜想思维导图的方式一定花费了作者很大的精力,特共享出来.原文:java基础思维导图 自己学习的查漏补缺如下 ...
- 20165223 week1测试查漏补缺
week1查漏补缺 经过第一周的学习后,在蓝墨云班课上做了一套31道题的小测试,下面是对测试题中遇到的错误的分析和总结: 一.背记题 不属于Java后继技术的是? Ptyhon Java后继技术有? ...
随机推荐
- Git 常用命令总结,掌握这些,轻松驾驭版本管理
原创 最近公司的代码管理工具要从SVN转到Git上,因此虽然之前用过Git,但是都是一些简单的推送提交,因此还是有必要进行一些系统的学习,这里做一下笔记,以备后询,且不定期更新. 关于SVN和Git的 ...
- STM32程序烧录总结
1.程序烧录方式 1)ST-LINK下载 2)SWD下载 SWD对应的引脚为:GND.RST.SWDIO.SWDCLK SWD与Jlink的比较 3)串口下载 串口下载不能直接在MDK点击Downlo ...
- XSS 4
第四题 进去后是这个样子的 然后我们随便输入第三题中的数据发现 不可以运行 看一下右边 发现()被替换成空内容 然后我们改一下 括号可以用倒引号替换 然后我们改一下 然后就通过了 原因:这个题是有关 ...
- Eclipse C++配置静态链接库和动态链接库
转:https://blog.csdn.net/iteye_20658/article/details/82650699 1.动态库: 一.创建动态链接库1.创建工程new->project-& ...
- C语言 多文件编程
C语言 多文件编程 分文件编程 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件 在头文件对应的xxx.c中实现xxx.h声明的函数 防止头文件重复包含 1.当一个项目比较大时,往往都是分文 ...
- 洛谷 P3901 数列找不同(莫队)
题目链接:https://www.luogu.com.cn/problem/P3901 这道题简单莫队模板题,然后$add$和$del$分别处理$vis[]$从$0-->1$和从$1--> ...
- samba文件共享及账户映射
samba文件共享及账户映射 实验介绍:在虚拟机Linux系统上安装sanmba服务,并在另外一台虚拟机的win7系统上访问共享文件夹,主要分为:匿名访问.身份验证访问.以及添加白名单和为了保护服务器 ...
- python爬虫中图形验证码的处理
使用python爬虫自动登录时,遇到需要输入图形验证码的情况,一个比较简单的处理方法是使用打码平台识别验证码. 使用过两个打码平台,打码兔和若快,若快的价格更便宜,识别率相当.若快需要注册两个帐号:开 ...
- UDP协议 sendto 和 recvfrom 浅析与示例
UDP(user datagram protocol)用户数据报协议,属于传输层. UDP是面向非连接的协议,它不与对方建立连接,而是直接把数据报发给对方.UDP无需建立类如三次握手的连接,使得通信效 ...
- HBase 2.1.3 集群 web 报错InvalidProtocolBufferException 解决方法
搭建好HBase 集群后,各种后台进程都正常,搭建手册参考: Hbase 2.1.3 集群搭建手册https://www.cndba.cn/dave/article/3322 但是通过web访问,却报 ...