主要回答一下几个问题

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:

  1. n++对应的汇编指令是3条.

    1.1 加载: 加载n到寄存器,

    1.2 更新: 更新寄存器(n+1)

    1.3 存储(写回内存): 把寄存器的值存储到内存中n对应的内存地址中

    参考<深入理解计算机系统第3版>12.3小节的图12-18b:

  2. c语言的多线程调度是抢占式的,多线程的上下文切换可以发生在任何指令之间(TODO除了少数原子指令)。

    所以c语言是有并发问题的。

  3. go的非抢占式调度携程, 上述代码只在函数调用时触发协程切换(go1.14版本以前,调用sleep时触发调用), 所以n++的3个指令可以一次执行完成,然后进入sleep才切换到另一个go携程,所以每个携程的n++是串行执行的,即使用1万个携程来测试也没有并发问题:

0++
sleep切换
1++
sleep切换
打印2,退出

总结:go在sleep时才发生协程切换,c语言的多线程切换可能发生在任何指令处,两者的切换粒度不一样。

TODO:go1.11具体是怎么一个非抢占式度。

Q: 为什么GO代码单核没有问题,多核有问题

参考附录1

  1. 在单核CUP的情况下,每个CPU只会运行一个go携程,并且每个go携程执行时不会中断n++的指令,所以至少这些携程的n++语句是串行执行的,所以不会有并发问题。
  2. 在多核的情况下,多个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

  1. 同时多个cpu读取到了相同的n,后提交的线程会把先提交的线程的n++结果覆盖掉,导致部分线程加1操作丢失。

Q: 加锁时如何解决c语言的多核多线程并发问题

A:

  1. 锁的两种底层原理

    总线锁:

    缓存锁:

https://studygolang.com/articles/18630

  1. window下搭建c语言运行环境
  2. vscode使用Code Runner插件运行程序
  3. C语言多线程中变量累加问题的分析

TODO

加锁的2种底层实现



然后加锁操作的话,对应图中就是对cpu总线加锁,使得同一时刻只有一个cpu能访问内存。但是这个效率比较低,于是有了基于cpu缓存的锁。

加锁的2种底层实现,我在这看的:https://mp.weixin.qq.com/s/RDEQSOjrSBVYVq6LV5MslQ

Q: 问:如何实现x++的原子性?

在单处理器上,如果执行x++时,禁止多线程调度,就可以实现原子。因为单处理的多线程并发是伪并发。

在多处理器上,需要借助cpu提供的Lock功能。锁总线。读取内存值,修改,写回内存三步期间禁止别的CPU访问总线。同时我估计使用Lock指令锁总线的时候,OS也不会把当前线程调度走了。要是调走了,那就麻烦了。

CPU中的原子操作

参考资料

  1. golang CAS原子操作和单核,多核并发问题

【协作式原创】查漏补缺之Go并发问题(单核多核)的更多相关文章

  1. Java查漏补缺(3)(面向对象相关)

    Java查漏补缺(3) 继承·抽象类·接口·静态·权限 相关 this与super关键字 this的作用: 调用成员变量(可以用来区分局部变量和成员变量) 调用本类其他成员方法 调用构造方法(需要在方 ...

  2. Java基础查漏补缺(2)

    Java基础查漏补缺(2) apache和spring都提供了BeanUtils的深度拷贝工具包 +=具有隐形的强制转换 object类的equals()方法容易抛出空指针异常 String a=nu ...

  3. CSS基础面试题,快来查漏补缺

    本文大部分问题来源:50道CSS基础面试题(附答案),外加一些面经. 我对问题进行了分类整理,并给了自己的回答.大部分知识点都有专题链接(来源于本博客相关文章),用于自己前端CSS部分的查漏补缺.虽作 ...

  4. Go语言知识查漏补缺|基本数据类型

    前言 学习Go半年之后,我决定重新开始阅读<The Go Programing Language>,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书 ...

  5. 《CSS权威指南》基础复习+查漏补缺

    前几天被朋友问到几个CSS问题,讲道理么,接触CSS是从大一开始的,也算有3年半了,总是觉得自己对css算是熟悉的了.然而还是被几个问题弄的"一脸懵逼"... 然后又是刚入职新公司 ...

  6. js基础查漏补缺(更新)

    js基础查漏补缺: 1. NaN != NaN: 复制数组可以用slice: 数组的sort.reverse等方法都会改变自身: Map是一组键值对的结构,Set是key的集合: Array.Map. ...

  7. Entity Framework 查漏补缺 (一)

    明确EF建立的数据库和对象之间的关系 EF也是一种ORM技术框架, 将对象模型和关系型数据库的数据结构对应起来,开发人员不在利用sql去操作数据相关结构和数据.以下是EF建立的数据库和对象之间关系 关 ...

  8. 2019Java查漏补缺(一)

    看到一个总结的知识: 感觉很全面的知识梳理,自己在github上总结了计算机网络笔记就很累了,猜想思维导图的方式一定花费了作者很大的精力,特共享出来.原文:java基础思维导图 自己学习的查漏补缺如下 ...

  9. 20165223 week1测试查漏补缺

    week1查漏补缺 经过第一周的学习后,在蓝墨云班课上做了一套31道题的小测试,下面是对测试题中遇到的错误的分析和总结: 一.背记题 不属于Java后继技术的是? Ptyhon Java后继技术有? ...

随机推荐

  1. Git 常用命令总结,掌握这些,轻松驾驭版本管理

    原创 最近公司的代码管理工具要从SVN转到Git上,因此虽然之前用过Git,但是都是一些简单的推送提交,因此还是有必要进行一些系统的学习,这里做一下笔记,以备后询,且不定期更新. 关于SVN和Git的 ...

  2. STM32程序烧录总结

    1.程序烧录方式 1)ST-LINK下载 2)SWD下载 SWD对应的引脚为:GND.RST.SWDIO.SWDCLK SWD与Jlink的比较 3)串口下载 串口下载不能直接在MDK点击Downlo ...

  3. XSS 4

    第四题 进去后是这个样子的 然后我们随便输入第三题中的数据发现 不可以运行 看一下右边 发现()被替换成空内容 然后我们改一下  括号可以用倒引号替换 然后我们改一下 然后就通过了 原因:这个题是有关 ...

  4. Eclipse C++配置静态链接库和动态链接库

    转:https://blog.csdn.net/iteye_20658/article/details/82650699 1.动态库: 一.创建动态链接库1.创建工程new->project-& ...

  5. C语言 多文件编程

    C语言 多文件编程 分文件编程 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件 在头文件对应的xxx.c中实现xxx.h声明的函数 防止头文件重复包含 1.当一个项目比较大时,往往都是分文 ...

  6. 洛谷 P3901 数列找不同(莫队)

    题目链接:https://www.luogu.com.cn/problem/P3901 这道题简单莫队模板题,然后$add$和$del$分别处理$vis[]$从$0-->1$和从$1--> ...

  7. samba文件共享及账户映射

    samba文件共享及账户映射 实验介绍:在虚拟机Linux系统上安装sanmba服务,并在另外一台虚拟机的win7系统上访问共享文件夹,主要分为:匿名访问.身份验证访问.以及添加白名单和为了保护服务器 ...

  8. python爬虫中图形验证码的处理

    使用python爬虫自动登录时,遇到需要输入图形验证码的情况,一个比较简单的处理方法是使用打码平台识别验证码. 使用过两个打码平台,打码兔和若快,若快的价格更便宜,识别率相当.若快需要注册两个帐号:开 ...

  9. UDP协议 sendto 和 recvfrom 浅析与示例

    UDP(user datagram protocol)用户数据报协议,属于传输层. UDP是面向非连接的协议,它不与对方建立连接,而是直接把数据报发给对方.UDP无需建立类如三次握手的连接,使得通信效 ...

  10. HBase 2.1.3 集群 web 报错InvalidProtocolBufferException 解决方法

    搭建好HBase 集群后,各种后台进程都正常,搭建手册参考: Hbase 2.1.3 集群搭建手册https://www.cndba.cn/dave/article/3322 但是通过web访问,却报 ...