很多人都是冲着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
}
同样的道理,把'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
}
那把上述程序反过来。
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的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
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()
}
这是可以处理一个或者多个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")
}