Go 的并发 Concurrency

来源:互联网 发布:qq飞车神圣天使数据 编辑:程序博客网 时间:2024/05/24 00:49

Go 的并发

              很多人都是冲着Go的高并发来学习Go的。而gorouting只是由官方实现的一个超级线程池而已,每个实例4-5KB的栈内存占用和由于实现机制而大幅减少创建和销毁的开销。这就是Go高并发的原因。


     Go中并发程序依靠的是两个:goroutine和channel。

     一个goroutine对新手来说可看作一个线程,但是他又不是一个线程。goroutine的出现正是为了替代原来的线程概念成为最小的调度单位。一旦运行goroutine时,先去当先线程查找,如果线程阻塞了,则被分配到空闲的线程,如果没有空闲的线程,那么就会新建一个线程。注意的是,当goroutine执行完毕后,线程不会回收推出,而是成为了空闲的线程。


    Gorouting奉行通过通信来共享内存。而不是共享内存来通信。


一个最简单的gorouting实现。

package mainimport "fmt"func main(){go Go()}func Go(){fmt.Println("Go Go GO !!!")}
只需要在调用函数前加一个 go 这个关键字就可以实现最简单的并发。但是执行结果试试。什么都没有输出。这是为什么??因为Go还没执行,main就执行结束了。所以没有打印。这时就需要一个chan了


那什么是chan呢?

channel:

--gorouting的沟通的桥梁。大都是阻塞同步的。

--通过make创建,close关闭。

--可以设置单向和双向通道,可以设置缓存大小,在未被填满之前不会发生阻塞。


下面简单的介绍通过chan阻塞的方式实现gorouting

package mainimport "fmt"func main(){c := make(chan bool)go func(){fmt.Println("Go Go Go !!!")c <- true}()<-c}

上面的创建了一个chan,内容存的是一个bool类型。在main函数的最后有一个<-c 。也就是c要往外扔一个数据,但c没数据可以扔啊,只有匿名函数里面的c传进去一个true这个bool型值时,main的最后才不会阻塞。


同样的道理,把'c <-' 和 c '<- ture' 调换个位置也可以,目的是阻塞他们呢。


chan是可以被迭代的。

package mainimport "fmt"func main(){c := make(chan bool)go func(){fmt.Println("Go Go Go !!!")c <- falseclose(c)  //必须关闭他}()for v := range c {fmt.Println(v)}}
main的最后在一直的迭代c。但是注意一定要close(c) 。要不然所有的gorouting都在等待,而变成了死锁。程序崩溃退出。


单向通道和双向通道。

make创建的chan是双向的,可存可取。

而单向的是只可以存,或者只可以写。


而缓存的。就是有空间的chan。比如缓存为10的通道,那么chan在没写满10的话,就可以一直写。

有缓存的话,在某些情况下是不阻塞的。

package mainimport "fmt"func main(){c := make(chan bool,1)go Go(c)<- c}func Go(c chan bool) {fmt.Println("Go Go Go !!!")c <- true}

注意,在make的最后设置缓存大小。这里是有缓存的,main的最后要扔出一个值,但没有值可扔。所以阻塞。

那把上述程序反过来。

package mainimport "fmt"func main(){c := make(chan bool,1)go Go(c)c <- true}func Go(c chan bool) {fmt.Println("Go Go Go !!!")<- c}
这段程序翻过来了,main的最后要读入一个值,因为有缓存了,你想什么时候读就什么时候读,不读都可以,所以main这里的chan就起不到阻塞的作用了。

下面看这个例子

package mainimport "fmt"func main(){c := make(chan bool)for i := 0;i <10 ;i++ {go Go(c,i)}<- c}func Go(c chan bool,index int) {a := 1for i := 0; i < 10000000; i++ {a += i}fmt.Println(index, a)if index == 9 {c <- true}}

起10个gorouting。当10个循环都开始了才给 c 里面扔进去个true。让main的 c 终止阻塞。

但是看看输出

4 499999950000016 499999950000019 49999995000001

这是怎么回事??

问题一:首先每次都是按顺序去执行。前面的顺序永远是从小到大。这就不像是并发啊。

可以通过判断CPU核数的方法。

package mainimport ("fmt""runtime")func main(){runtime.GOMAXPROCS(runtime.NumCPU())  //判断CPU核数c := make(chan bool)for i := 0;i <10 ;i++ {go Go(c,i)}<- c}func Go(c chan bool,index int) {a := 1for i := 0; i < 10000000; i++ {a += i}fmt.Println(index, a)if index == 9 {c <- true}}
执行结果:

3 499999950000016 499999950000017 499999950000014 499999950000018 499999950000019 49999995000001

问题二:输出明显不够10个啊

找个缓存为10的chan。读10次不久解决了吗

package mainimport ("fmt""runtime")func main(){runtime.GOMAXPROCS(runtime.NumCPU())c := make(chan bool,10)for i := 0;i <10 ;i++ {go Go(c,i)}for i := 0;i < 10;i++ {<-c}}func Go(c chan bool,index int) {a := 1for i := 0; i < 10000000; i++ {a += i}fmt.Println(index, a)c <- true}

看看输出

0 499999950000014 499999950000011 499999950000019 499999950000017 499999950000012 499999950000016 499999950000013 499999950000015 499999950000018 49999995000001

解决方法2:

sync。创建一个waitgroup . 创建10个任务。每完成一个任务,就会Done一下。main的最后在Wait。只有创建的10个任务全部都Done了,才会main退出结束。

package mainimport ("fmt""runtime""sync")func main(){runtime.GOMAXPROCS(runtime.NumCPU())wg := sync.WaitGroup{}wg.Add(10)for i := 0;i <10 ;i++ {go Go(&wg,i)}wg.Wait()}func Go(wg *sync.WaitGroup,index int) {a := 1for i := 0; i < 10000000; i++ {a += i}fmt.Println(index, a)wg.Done()}

select 呢

这是可以处理一个或者多个channel的发送与接收。

同时有多个可用的channel时按随机顺序处理。

可用空的select来阻塞main函数。

可设置超时。    其实和IO操作的select,poll,epoll那个select有点相似。


ch1 := make (chan int, 1)ch2 := make (chan int, 1)select {case <-ch1:    fmt.Println("ch1 pop one element")case <-ch2:    fmt.Println("ch2 pop one element")default:    fmt.Println("default")}

还和switch有一点相似。








原创粉丝点击