Go 的并发 Concurrency

Go 的并发

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


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

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


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


一个最简单的gorouting实现。

package main
import "fmt"

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


那什么是chan呢?

channel:

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

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

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


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

package main
import "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 main
import "fmt"

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


单向通道和双向通道。

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

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


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

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

package main
import "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 main
import "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 main
import "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 := 1
	for i := 0; i < 10000000; i++ {
		a += i
	}
	fmt.Println(index, a)
	if index == 9 {
		c <- true
	}
}

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

但是看看输出

4 49999995000001
6 49999995000001
9 49999995000001

这是怎么回事??

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

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

package main

import (
	"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 := 1
	for i := 0; i < 10000000; i++ {
		a += i
	}
	fmt.Println(index, a)
	if index == 9 {
		c <- true
	}
}
执行结果:

3 49999995000001
6 49999995000001
7 49999995000001
4 49999995000001
8 49999995000001
9 49999995000001

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

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

package main

import (
	"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 := 1
	for i := 0; i < 10000000; i++ {
		a += i
	}
	fmt.Println(index, a)
	c <- true
}

看看输出

0 49999995000001
4 49999995000001
1 49999995000001
9 49999995000001
7 49999995000001
2 49999995000001
6 49999995000001
3 49999995000001
5 49999995000001
8 49999995000001

解决方法2:

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

package main
import (
	"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 := 1
	for 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有一点相似。








你可能感兴趣的:(Go)