Golang在语言级别支持了协程,由runtime进行管理。

在Golang中并发执行某个函数非常简单:

func Add(x, y int) {
fmt.Println(x + y)
} func RunRoutine() {
for i := 0; i < 10; i++ {
go Add(i, i)
}
}

但是输出为空。

因为虽然新建了协程调用Add函数,但是该协程还没有来得及执行,程序就结束了。所以输出为空。

如果想让代码按预想的方式运行,就需要让主函数等待所有goroutine退出后再结束。这就引出了goroutine间通信的问题。

首先,我们先用最简单粗暴,也是传统的Lock来解决。

这时的思路是:我们加一个全局的变量count。每次调用了Add,我们就count++。这样,当count=10的时候,就说明10个goroutine都执行完成了。

但是,全局变量的访问需要加锁,这样才能保证count的访问是安全的。

代码如下:

var (
lock *sync.Mutex
count int
) func Add(x, y int) {
lock.Lock()
defer lock.Unlock()
fmt.Println(x + y)
count++
} func RunRoutine() {
lock = &sync.Mutex{}
count = 0
for i := 0; i < 10; i++ {
go Add(i, i)
}
for {
lock.Lock()
temp := count
if temp >= 10 {
break
}
lock.Unlock()
}
}

这样,执行结果如下:

2
4
18
10
12
14
6
16
0
8

根据结果来看,10个协程都执行结束了,并且10个协程的执行顺序也是随机的。

但是,事情貌似变得糟糕了。我们为了实现一个简单的功能,却写出了非常复杂的代码。

如果用Golang来解决呢,这时我们考虑用channel来解决问题。

channel是Golang提供的goroutine间的通信方式。我们可以使用channel在多个goroutine间传递消息。当然,channel是进程内的通信方式,如果需要进程间通信,可能Socket或者HTTP通信协议更合适。

先看看,如果用channel,如果解决上面的问题。

var (
chs []chan int
) func Add(x, y int) {
fmt.Println(x + y)
chs[x] <- 1
} func RunRoutine() {
chs = make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Add(i, i)
}
for _, ch := range chs {
<-ch
}
}

这里,我们定义了一个10个元素的channel数组。每次调用Add时,我们在对应的channel中写入一个数据。最后,我们在RunRoutine中遍历了整个数组,当所有的channel都读取完数据,说明10个goroutine都运行结束了。

现在,我们看看channel的语法:

channel的声明:

var chanName chan ElementType

例如: var ch chan int

channel的初始化:

可以利用make对channel进行初始化:

ch = make(chan int)

ch = make(chan int, 1)

前者初始化了一个无缓冲的channel。无缓冲即当某个协程在channel中写入了数据,就马上被阻塞,要等到其他协程消费了该数据,协程才会继续执行。

后者初始化了一个有缓冲的channel,缓冲长度为1。即ch为空时,当某个协程往ch中写入数据,并不会马上阻塞。当ch内有1个数据时,再往ch中写入数据,即会马上阻塞,知道协程消费了数据,使ch内的数据小于等于其缓冲量。

有缓冲的channel可以用range进行读取。

channel的读写:

ch <- 1

i := <- ch

总之就是用箭头来进行读写操作,很直观。

需要注意的就是读写操作带来的阻塞。

select:

select是类似switch的,用来处理channel异步IO的问题。

    select {
case <- ch:
case ch <- 1:
default:
}

需要注意的是,多个case同时符合的时候,switch是按顺序执行的,select是随机选择一个分支执行的。

channel的超时机制:

Golang的channel并没有自带的超时机制,但是可以用select来实现。

考虑以下的几种方法:

    timeout := make(chan bool, 1)
go func() {
time.Sleep(time.Second)
timeout <- true
}()
select {
case <- ch:
case <- timeout:
}

该方法提供了一个timeout,利用协程sleep 1s之后向timeout中写入一个true。当select中的ch在1秒内没有读出数据时,timeout将读出数据。

    select {
case <- ch:
case <-time.After(time.Second):
}

该方法利用了time.After。该方法的问题在于,这个计时器在select执行之后仍在在runtime中存在。这在高并发的应用场景下会产生性能问题。

to := time.NewTimer(time.Second)
for {
to.Reset(time.Second)
select {
case <-c:
case <-to.C:
}
}

该方法算是上一种方法的改进。该方法利用了一个全局的timer,这样可以避免高并发下的计时器的滥用。

单向channel:

    var (
ch1 chan int
ch2 chan<- int
ch3 <-chan int
)

ch1是普通channel,ch2是只写channel,ch3是只读channel。

    ch4 := make(chan int)
ch5 := <-chan int(ch4)
ch6 := chan<- int(ch4)

上面是各种channel之间的类型转换。

关闭channel:

close(ch)

好像并没有什么其他可说的。唯一一点是,我们可以利用多返回值,在读取channel的时候检查channel是否被关闭。

val, ok := <-ch

多核并行:

可以利用如下命令进行设置。但是Golang是否真的可以利用多个核心,还需要实际验证。

runtime.GOMAXPROCS(16)

同步锁:

最开始的时候已经利用锁实现了功能。需要注意的是Lock和Unlock的对应。

唯一性操作:

sync.Once(funcName)

思考了一下,大概可以用来实现单例模式。

Go语言学习笔记(4)——并发编程的更多相关文章

  1. 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解

    误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...

  2. (转)《深入理解java虚拟机》学习笔记10——并发编程(二)

    Java的并发编程是依赖虚拟机内存模型的三个特性实现的: (1).原子性(Atomicity): 原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的. ...

  3. Python Web学习笔记之并发编程IO模型

    了解新知识之前需要知道的一些知识 同步(synchronous):一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行 #所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调 ...

  4. (转)《深入理解java虚拟机》学习笔记9——并发编程(一)

    随着多核CPU的高速发展,为了充分利用硬件的计算资源,操作系统的并发多任务功能正变得越来越重要,但是CPU在进行计算时,还需要从内存读取输出,并将计算结果存放到内存中,然而由于CPU的运算速度比内存高 ...

  5. Python Web学习笔记之并发编程的孤儿进程与僵尸进程

    1.前言 之前在看<unix环境高级编程>第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊.今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎么解决,我只停留在概念上 ...

  6. 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

    在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...

  7. Go语言学习笔记(1)——顺序编程

    Go语言学习笔记这一堆主要是<Go语言编程>(人民邮电出版社)的读书笔记.中间会穿插一些零碎的点,比如源码学习之类的.大概就是这样吧. 1. 顺序编程 1.1 变量 变量的声明: var ...

  8. HTML语言学习笔记(会更新)

    # HTML语言学习笔记(会更新) 一个html文件是由一系列的元素和标签组成的. 标签: 1.<html></html> 表示该文件为超文本标记语言(HTML)编写的.成对出 ...

  9. 2017-04-21周C语言学习笔记

    C语言学习笔记:... --------------------------------- C语言学习笔记:学习程度的高低取决于.自学能力的高低.有的时候生活就是这样的.聪明的人有时候需要.用笨的方法 ...

  10. 2017-05-4-C语言学习笔记

    C语言学习笔记... ------------------------------------ Hello C语言:什么是程序:程序是指:完成某件事的既定方式和过程.计算机中的程序是指:为了让计算机执 ...

随机推荐

  1. 一文掌握XSS

    目录 XSS跨站脚本攻击 1.什么叫跨站脚本攻击? 2.XSS跨站脚本攻击的原理 3.XSS跨站脚本攻击的目的是什么? 4.XSS跨站脚本攻击出现的原因 5.XSS跨站脚本攻击的条件 1.有输入有输出 ...

  2. Redis+LUA整合使用

    .前言 从本章节开始我们就开始讲解一些 Redis 的扩展应用了,之前讲的主从.哨兵和集群都相当重要,也许小公司用不到集群这么复杂的架构,但是也要了解各知识点的原理,只要了解了原理,无论什么时候是有, ...

  3. 循序渐进VUE+Element 前端应用开发(33)--- 邮件参数配置和模板邮件发送处理

    在系统处理中,有时候需要发送邮件通知用户,如新增用户的邮件确认,密码找回,以及常规订阅消息.通知等内容处理,都可以通过邮件的方式进行处理.本篇随笔介绍结合VUE+Element 前端,实现系统的邮件参 ...

  4. LeetCode 面试题16.18.模式匹配

    模式匹配 题目: 你有两个字符串,即pattern和value. pattern字符串由字母"a"和"b"组成,用于描述字符串中的模式.例如,字符串" ...

  5. 【JavaWeb】JavaScript 基础

    JavaScript 基础 事件 事件是指输入设备与页面之间进行交互的响应. 常用的事件: onload 加载完成事件:页面加载完成之后,常用于页面 js 代码初始化操作: onclick 单击事件: ...

  6. 【Flutter】可滚动组件之滚动控制和监听

    前言 可以用ScrollController来控制可滚动组件的滚动位置. 接口描述 ScrollController({ // 初始滚动位置 double initialScrollOffset = ...

  7. Windows程序通用自动更新模块(C#,.NET4.5以上)

    本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf. 一.工作流程:1. 主程序A调起升级程序B2. B从服务器获取更新程序列表,打印更新信息.3. B ...

  8. Eclipse中给jar包导入JavaDoc的方法

    原文转载自:http://blog.csdn.net/mr_von/article/details/7740138 在使用Java语言开发的过程中,开发人员经常需要用到一些开源的工具包.在使用别人的j ...

  9. Ice系列--傻瓜式服务开发IceBox

    前言 相信大家在没有接触过框架之前,都自己或多或少的开发过一些应用服务.每个应用服务除了业务配置还有很多环境配置,资源配置等,这些跟部署相关的配置.服务跟配置文件是一种静态绑定的方式,更新配置还需要重 ...

  10. QTextEdit的paste

    By 鬼猫猫 20130117 http://www.cnblogs.com/muyr/ 背景 QTextEdit中粘贴一大段文字时,EasyDraft中粘贴进去的文字们的格式就乱了,处于无格式.还有 ...