`
daihongtao110121
  • 浏览: 15170 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

golang之并发编程

 
阅读更多
       越来越多的人开始注意到golang的高效性,于是很公司开始使用golang去做业务。这方面七牛成为了第一个吃螃蟹的公司。听说golang的高效,就是因为在并发时使用了比线程还要小的协程。至于为什么会可以达到这个效果,作为初学者的我还是不懂
一:什么是并发
简单来讲,并发就是让多个任务可以同时去执行,而不是过程式的必须从前往后来,并且一次只能执行一个任务。当前比较主流的并发实现模式有如下几种
1.多进程
。操作系统层面的模式
2.多线程。大部分在操作系统上,也是使用最多的
3.基于回调的非阻塞/异步IO
4.协程。是一种用户态线程,不需要操作系统来进行抢占式调用,但又在真正的实现中寄存于线程中,系统开销极小。非常高效的实现任务的并发性

二:协程goroutine
goroutine是Go语言中的轻量级线程实现,由Go运行时管理。这些书面的说起来也难以理解。下面我们看一个例子-试下一个Add函数,把两个参数相加,并把结果打印如下
func Add(x, y int) int{
	z := x + y
        fmt.Println(z)
	return z
}
那么在golang中我们怎么去让这个函数并发的去执行呢?go关键字出现了,他是语言本身最重要的关键字,在函数前面添加go就是让函数并发执行!下面我们看看效果吧!
package main

import "fmt"

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

按照常理这个时候我们就会说,“看,接着我就要打印10次了!”跑一遍试试。

怎么什么也没有呢?难道没有执行吗?到底是什么原因产生了这种令人难以捉摸的现象呢?就是因为在调用Add()函数的时候前面的那个关键字起作用了,当不加关键字的时候程序都是在main这个主函数中执行,然后按照顺序来执行,所以就会打印十次。但是go 的出现让它成为了另一个线程与主线程去并发执行了。go的执行机制是从main开始,它又启动了10个协程序。当它执行完返回时,协程还没有来得及执行。所以就什么也没打印了!现在懂了吧。是不是很有趣呢!既然知道了,我们应该怎么去保证等10个协程执行完呢?
解决方法一(共享内存)也就是其他语言中普遍解决的原理:直接上代码
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
		}
	}
}

该例子中我们用到了一个全局变量counter计数器。同时还需要在协程执行方法时候进行锁定不让其他协程序执行!这样就保证了一个协程执行一次计数器就增加一。而在主程序中一直去变量计数器,直到达到10(也就是10个协程都执行完毕)退出。这样就能得到我们想要的结果了!


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

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


//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




通过上面的图我们可以知道channel的大致流程,但在这里值得注意的就是,在写入和读的时候他们是阻塞的,也就是保证了只有一个线程进行读写操作,这样是不是不用进行繁琐的同步锁的过程了呢。说了这么多.在来看看例子.
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)
}

结果如下:



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

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


对于第三种解释还是不太清楚。注释得也不纤细,希望大家自行验证!

  • 大小: 12.5 KB
  • 大小: 6.4 KB
  • 大小: 13.3 KB
  • 大小: 16.6 KB
0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics