golang之并发编程

[size=medium][color=indigo] 越来越多的人开始注意到golang的高效性,于是很公司开始使用golang去做业务。这方面七牛成为了第一个吃螃蟹的公司。听说golang的高效,就是因为在并发时使用了比线程还要小的协程。至于为什么会可以达到这个效果,作为初学者的我还是不懂[/color][/size]
[size=large][color=darkblue][b]一:什么是并发[/b][/color][/size]
[size=medium]简单来讲,并发就是让多个任务可以同时去执行,而不是过程式的必须从前往后来,并且一次只能执行一个任务。当前比较主流的并发实现模式有如下几种[b]
1.多进程[/b]。操作系统层面的模式
[b]2.多线程[/b]。大部分在操作系统上,也是使用最多的
[b]3.基于回调的非阻塞/异步IO[/b]
[b]4.协程[/b]。是一种用户态线程,不需要操作系统来进行抢占式调用,但又在真正的实现中寄存于线程中,系统开销极小。非常高效的实现任务的并发性
[/size]
[b][color=indigo][size=large]二:协程goroutine[/size][/color][/b]
[size=medium]goroutine是Go语言中的轻量级线程实现,由Go运行时管理。这些书面的说起来也难以理解。下面我们看一个例子-试下一个Add函数,把两个参数相加,并把结果打印如下[/size]
func Add(x, y int) int{
z := x + y
fmt.Println(z)
return z
}
[size=medium][color=orange]那么在golang中我们怎么去让这个函数并发的去执行呢?[/color]go关键字出现了,他是语言本身最重要的关键字,在函数前面添加go就是让函数并发执行!下面我们看看效果吧![/size]
package main

import "fmt"

func main(){
for i:=0;i<10;i++{
go Add(i,i)
}
}

[size=medium]按照常理这个时候我们就会说,“看,接着我就要打印10次了!”跑一遍试试。[/size]
[img]http://dl2.iteye.com/upload/attachment/0099/6856/caac7bf6-8fdb-3a0a-acfc-f6bf5cf40506.jpg[/img]
[size=medium]怎么什么也没有呢?难道没有执行吗?到底是什么原因产生了这种令人难以捉摸的现象呢?就是因为在调用Add()函数的时候前面的那个关键字起作用了,当不加关键字的时候程序都是在main这个主函数中执行,然后按照顺序来执行,所以就会打印十次。但是go 的出现让它成为了另一个线程与主线程去并发执行了。go的执行机制是从main开始,它又启动了10个协程序。当它执行完返回时,协程还没有来得及执行。所以就什么也没打印了!现在懂了吧。是不是很有趣呢!既然知道了,我们应该怎么去保证等10个协程执行完呢?[/size]
[b][color=indigo][size=medium]解决方法一(共享内存)也就是其他语言中普遍解决的原理:直接上代码[/size][/color][/b]
package main

import (
"fmt"
"runtime"
"sync"
)

/***定义一个计数器,记录协程运行完成的个数*/
var counter int = 0

func Add(x, y int) int {
z := x + y
return z
}

/****记录协程执行个数的方法,每完成一个计数器就添加1*/
func Count(lock *sync.Mutex, x int, y int) {
lock.Lock()
counter++
fmt.Println(Add(x, y))
lock.Unlock()

}

func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock, i, i)
}

//让主程序进行等待,直到10个协程序都完成后才退出主程序
for {
lock.Lock()
c := counter
lock.Unlock()

runtime.Gosched()
if c >= 10 {
break
}
}
}

[size=medium]该例子中我们用到了一个全局变量counter计数器。同时还需要在协程执行方法时候进行锁定不让其他协程序执行!这样就保证了一个协程执行一次计数器就增加一。而在主程序中一直去变量计数器,直到达到10(也就是10个协程都执行完毕)退出。这样就能得到我们想要的结果了![/size]
[img]http://dl2.iteye.com/upload/attachment/0099/6858/8280c404-cc10-3251-8505-1a109179cb9c.jpg[/img]

[size=medium]结果我们是得到了?但是这样真的好吗?是不是代码显得很浮肿?明明就是一个简单的东西还得写这么多代码?于是go就结束了这种笨重的方式。使用到了并发的另一种方式---协程间的通信(channel)[/size]

[size=medium][color=indigo][b]并发的通信方式channel[/b][/color]
channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数行为要一致
基本语法[/size]


//1.声明格式
var chanName chan ElementType

//2.也可以声明一个map形式
var m map[string] chan bool


//3.构造一个channel
ch:=make(chan int)


//4.将数据写入到channel
ch<-value


//5.读取数据
value:=<-ch


[img]http://dl2.iteye.com/upload/attachment/0099/6862/b607d8ee-d4e2-37d0-9c4e-3fe21d9cb0e3.jpg[/img]

[size=medium][color=red]通过上面的图我们可以知道channel的大致流程,但在这里值得注意的就是,在写入和读的时候他们是阻塞的,也就是保证了只有一个线程进行读写操作,这样是不是不用进行繁琐的同步锁的过程了呢。说了这么多.在来看看例子.[/color][/size]
package main

import (
"fmt"
"strconv"
//"time"
)

func Count(ch chan int) {
ch <- 345
fmt.Println("Counting")
}

func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {

/**这里在创建的时候,默认是没有缓存的,所以若要想Count()函数里都印出来的话,需要将主线程停一段时间*/
chs[i] = make(chan int)
go Count(chs[i])
}

for _, ch := range chs {
value := <-ch
fmt.Println("value:", value)
}

//time.Sleep(1 * 1e9)
}

[size=medium]结果如下:[/size]

[img]http://dl2.iteye.com/upload/attachment/0099/6864/3c20c6aa-a282-37ee-8815-296949bfa043.jpg[/img]

[size=medium][color=indigo]分析:看见这样的结果我们会觉得很奇怪?为什么读取到了10个值,但是却只打印了一个counting.不是10个都写进去了么?为什么只打印出来一个呢?想了很久?也试了很久最后得出结论-10个协程在写完的时候,主程序也在循环进行读的操作,当协程序写完数据还没执行到打印语句的时候,结果主线程已经读完了!这样就导致了只打印一次了?个人认为一次只是偶然,可以是几次!解决办法想到了三种:
1.就是我注释掉的,让主线程进行休眠一段时间等待
2.将第10行和第11行代码交换位置,先打印然后去写
3.在19行代码修改为[/color][/size]

//后面添加1后,设置了缓存,等所有的协程都写完后,主线程才开始进行读操作
chs[i] = make(chan int,1)


[size=medium]对于第三种解释还是不太清楚。注释得也不纤细,希望大家自行验证![/size]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值