package main

import (
"fmt"
"sync"
) /*
Go实现了两种并发形式,第一种是大家普遍认知的多线程共享内存,其实就是 Java 或 C++ 等语言中的多线程开发;另外一种是Go语言特有的,也是Go语言推荐的 CSP(communicating sequential processes)并发模型。 CSP 并发模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享 channel(管道)进行通信的并发模型。 Go语言就是借用 CSP 并发模型的一些概念为之实现并发的,但是Go语言并没有完全实现了 CSP 并发模型的所有理论,仅仅是实现了 process 和 channel 这两个概念。 process 就是Go语言中的 goroutine,每个 goroutine 之间是通过 channel 通讯来实现数据共享。 这里我们要明确的是“并发不是并行”。并发更关注的是程序的设计层面,并发的程序完全是可以顺序执行的,只有在真正的多核 CPU 上才可能真正地同时运行;并行更关注的是程序的运行层面,并行一般是简单的大量重复,例如 GPU 中对图像处理都会有大量的并行运算。 为了更好地编写并发程序,从设计之初Go语言就注重如何在编程语言层级上设计一个简洁安全高效的抽象模型,让开发人员专注于分解问题和组合方案,而且不用被线程管理和信号互斥这些烦琐的操作分散精力。 在并发编程中,对共享资源的正确访问需要精确地控制,在目前的绝大多数语言中,都是通过加锁等线程同步方案来解决这一困难问题,而Go语言却另辟蹊径,它将共享的值通过通道传递(实际上多个独立执行的线程很少主动共享资源)。 并发编程的核心概念是同步通信,但是同步的方式却有多种。先以大家熟悉的互斥量 sync.Mutex 来实现同步通信,示例代码如下所示:
*/ /*
由于 mu.Lock() 和 mu.Unlock() 并不在同一个 Goroutine 中,所以也就不满足顺序一致性内存模型。同时它们也没有其他的同步事件可以参考,也就是说这两件事是可以并发的。 因为可能是并发的事件,所以 main() 函数中的 mu.Unlock() 很有可能先发生,而这个时刻 mu 互斥对象还处于未加锁的状态,因而会导致运行时异常。
*/ //func main() {
// var wg sync.WaitGroup
// var mu sync.Mutex
// wg.Add(1)
// go func() {
// defer wg.Done()
// fmt.Println("hello")
// mu.Lock()
// }()
// wg.Wait()
// //如果main的goroutine不等待就会报错
// //time.Sleep(time.Second * 5)
// mu.Unlock()
//} //这样也能运行
//func main() {
// var mu sync.Mutex
//
// go func() {
// fmt.Println("hello")
// mu.Unlock()
// }()
//
//} //func main() {
// go func() {
// fmt.Println("hello")
// }()
//} /*
运行结果
hello
fatal error: sync: unlock of unlocked mutex */ //func main() {
// var mu sync.Mutex
// mu.Lock()
// go func() {
// fmt.Println("hello")
// mu.Unlock()
// }()
// mu.Lock()
//} /*
修复的方式是在 main() 函数所在线程中执行两次 mu.Lock(),当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,main() 函数的阻塞状态驱动后台线程继续向前执行。 当后台线程执行到 mu.Unlock() 时解锁,此时打印工作已经完成了,解锁会导致 main() 函数中的第二个 mu.Lock() 阻塞状态取消,此时后台线程和主线程再没有其他的同步事件参考,它们退出的事件将是并发的,在 main() 函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。
*/ //使用 sync.Mutex 互斥锁同步是比较低级的做法,我们现在改用无缓存通道来实现同步:
//func main() {
// done := make(chan int)
// go func() {
// fmt.Println("hello")
// h := <-done
// fmt.Println("hhhhhhhhhhhhhh", h, '\n')
// }()
// done <- 1
// done <- 2
//} /*
根据Go语言内存模型规范,对于从无缓存通道进行的接收,发生在对该通道进行的发送完成之前。因此,后台线程<-done 接收操作完成之后,main 线程的done <- 1 发送操作才可能完成(从而退出 main、退出程序),而此时打印工作已经完成了。 上面的代码虽然可以同步,但是对通道的缓存大小太敏感,如果通道有缓存,就无法保证main()函数退出之前后台线程
就能正常打印了,更好的做法是将通道的发送和接收方向调换一下,这样可以避免同步事件受通道缓存大小的影响
*/ //func main() {
// done := make(chan int,1)
// go func() {
// fmt.Println("hello")
// done <- 1
// done <- 2
// }()
// <-done
//}
/*
对于带缓存的通道,对通道的第K个接收完成操作发生在第K+C个发送之前,其中C是通道的的缓存大小。虽然通道是带缓存的。
但是main线程接收完成是在后台线程发送开始但是还未完成的时刻此时打印工作也是已经完成的
*/ //基于带缓存通道,我们可以很容易将打印线程扩展到N个,下面的示例时开启十个后台线程分别打印 //func main() {
// done := make(chan int, 10) //带10个缓存
// /*开启N个后台打印线程*/
// for i := 0; i < cap(done); i++ {
// go func() {
// fmt.Println("hello")
// done <- 1
// }()
// }
//
// //等待N个后台线程完成
// for i := 0; i < cap(done); i++ {
// <-done
// }
//} //对于这种要等待N个线程完成后再进行下一步的同步操作有一个简单的方法,就是使用sync.WaitGroup来等待一组事件
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ { wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello")
}()
}
wg.Wait()
}
/*
其中 wg.Add(1) 用于增加等待事件的个数,
必须确保在后台线程启动之前执行(如果放到后台线程之中执行则不能保证被正常执行到)。
当后台线程完成打印工作之后,
调用 wg.Done() 表示完成一个事件,main() 函数的 wg.Wait() 是等待全部的事件完成。
*/

每日一抄 Go语言通信顺序进程简述的更多相关文章

  1. 09. Go 语言并发

    Go 语言并发 并发指在同一时间内可以执行多个任务.并发编程含义比较广泛,包含多线程编程.多进程编程及分布式程序等.本章讲解的并发含义属于多线程编程. Go 语言通过编译器运行时(runtime),从 ...

  2. 每日一问:简述 View 的绘制流程

    Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...

  3. 利用windows服务+timer或者windows任务计划程序+控制台进行进行每日邮件推送

    1.邮件发送代码 using System.Text; using System.Net; using System.Net.Mail; using System.Reflection; using ...

  4. Beta版本敏捷冲刺每日报告——Day1

    1.情况简述 Beta阶段第一次Scrum Meeting 敏捷开发起止时间 2017.11.2 08:00 -- 2017.11.2 21:00 讨论时间地点 2017.11.2晚6:00,软工所实 ...

  5. 老男孩IT教育-每日一题汇总

    老男孩IT教育-每日一题汇总 第几天 第几周 日期 快速访问链接 第123天 第二十五周 2017年8月25日 出现Swap file….already exists以下错误如何解决? 第122天 2 ...

  6. GoCN每日新闻(2019-11-07)

    GoCN每日新闻(2019-11-07) GoCN每日新闻(2019-11-07) 1. [译] 排序运行时间能否做到 O(n)?让 Go 语言来告诉你 https://mp.weixin.qq.co ...

  7. [每日一题]ES6中为什么要使用Symbol?

    关注「松宝写代码」,精选好文,每日面试题 加入我们一起学习,day day up 作者:saucxs | songEagle 来源:原创 一.前言 2020.12.23日刚立的flag,每日一题,题目 ...

  8. 《MySQL面试小抄》查询缓存机制终面

    <MySQL面试小抄>查询缓存机制终面 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点 ...

  9. 《MySQL面试小抄》索引考点二面总结

    <MySQL面试小抄>索引考点二面总结 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点 ...

  10. 【Java每日一题】20170106

    20170105问题解析请点击今日问题下方的"[Java每日一题]20170106"查看(问题解析在公众号首发,公众号ID:weknow619) package Jan2017; ...

随机推荐

  1. 八、常用Api

    Object 深拷贝和浅拷贝 Objects 包装类 StringBuilder StringJoin Math System RuntimeBigDecimal Date SImpleDateFor ...

  2. canvas 学习笔记

    1.利用上下文对象进行绘制画笔 var canvas=canvas.getContext('2d') 2.绘制路径 canvas.rect(30,30,300,300) 3.填充 canvas.fil ...

  3. Java笔记_方法重载

    /** * @ClassName OverLoadExercise * @Description TODO * @Author Orange * @Date 2021/4/19 8:29 * @Ver ...

  4. CCF 201909-1 小明种苹果

    #include <iostream> #include <bits/stdc++.h> #include <string> using namespace std ...

  5. linux下安装jdk8,nginx

    jdk8(官网下载的是jdk-8u231-linux-x64.tar.gz) 1.在/usr/local这路径下建一个jdk的文件夹,将下载好的jdk-8u231-linux-x64.tar.gz上传 ...

  6. 15、java递归解决迷宫问题

    递归真是一个.看着简单,听着简单,写不出来,想不到.以前也不是不理解递归,也不是看不懂递归的代码,但说实话真的很难想到自己去用这个递归也很难理清楚这个递归到底从哪里开始到哪里结束,将哪个步骤作为 一个 ...

  7. vue+vant-ui小程序,微信小程序自定义导航栏(适配刘海屏)

    整理一下微信小程序自定义导航栏和刘海屏适配问题 1.首先在根据官方文档,我们在小程序修改 app.json 中的 window 的属性 "navigationStyle": &qu ...

  8. 【转载】 VCS编译的基本参数,//code细节,注释

    https://www.bilibili.com/read/cv18255106?spm_id_from=333.999.0.0

  9. (0501)phase机制

    (1)启动seq: (2) 0312:

  10. 关于在ItelliJ IDEA社区版找不到Spring Initializr

    搜了好几个版本都没找到,太难顶了... 打开「ItelliJ IDEA社区版」→「Configure」→「Plugins」→搜索框搜索「Spring Assistant」→「Install」 还要配置 ...