主要回答一下几个问题

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. The Reason Why Cosmetic Airless Bottles Are Widely Used

    The contents of the Cosmetic Airless Bottles    can be isolated from the air, to prevent the product ...

  2. bugku 好多压缩包

    https://www.cnblogs.com/WangAoBo/p/6951160.html

  3. js实现Set

    class MySet { constructor(params) { if (typeof params[Symbol.iterator] !== 'function') { throw new T ...

  4. Hello 2020D(多重集)

    如果有一对时间对在某一场馆有时间重合而这一对时间对在另一场馆没有时间重合,则输出NO,否则输出YES. #define HAVE_STRUCT_TIMESPEC #include<bits/st ...

  5. Dart语言学习(四)Dart字符串

    一.字符串的创建 使用 单引号,双引号 创建字符串 使用 三个引号或双引号 创建多行字符串 使用 r 创建原始 raw 字符串 String str1 = 'Hello';//"" ...

  6. Spring Boot框架 - application.properties配置

    阅读此文章之前,需要参考 https://www.cnblogs.com/mysummary/p/12238242.html 创建Spring Boot项目 建成后目录如下 一.在com.demo.s ...

  7. Excel数据可视化方法

    目录: Excel图表基础: 1.选择要为其创建图表的数据,如: 2.单击“插入”菜单中的“推荐的图表”(也可点击右下角的下拉箭头),点击后选择所有图表即可查看所有的图标类型 3.选择所要的图表,单击 ...

  8. Rsync+inotify数据同步

    安装环境 备份服务器端:CentOS7,IP:192.168.1.100 备份客户端:CentOS7,IP:192.168.1.200 服务器端Rsync服务部署 1.安装程序包 # yum –y i ...

  9. 【PAT甲级】1100 Mars Numbers (20 分)

    题意: 输入一个正整数N(<100),接着输入N组数据每组包括一行字符串,将其翻译为另一个星球的数字. AAAAAccepted code: #define HAVE_STRUCT_TIMESP ...

  10. 【Python下进程同步之互斥锁、信号量、事件机制】

    " 一.锁机制:  multiprocess.Lock 上篇博客中,我们千方百计实现了程序的异步,让多个任务同时在几个进程中并发处理,但它们之间的运行没有顺序.尽管并发编程让我们能更加充分的 ...