并发和并行
并发是只一次做多件事情,但是同一时间只做一件事情
并行是一次做多件事情,并且这些事情在同一时间内都在处理
在函数前加入go
关键字来新建一个并发的Goroutines
如go funcName(1)
任何Gorountines都依附在main goroutines上,因此主进程结束时所有Goroutines都会立即结束,即使没有处理完毕
channels
类型前加入chan关键字来声明,如var a chan int
a := make(chan int)
前者a == nil
,后者则定义了一个名为a的channel
channel的数据读写1
2data := <- a//从a中读取数据,直到读取到数据才往下运行
a <- data //向a中写入数据
用法举例1
2
3
4
5
6
7
8
9
10func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
若channels写入数据但是该通道没有地方读取该数据,将会造成deadlock错误
参数传入时,可以使用chan<- int
,把传入的双向通道改为只写通道,函数内只能向通道写入数据,不能读取数据
通道中的每一条消息只能被收到一次,即使有多个函数试图接收,也只会有一个收到
可以使用’close(ch)’关闭ch通道,此时所有没有收到的都会收到关闭的数据
·v,ok<-ch的ok来判断ch通道是否是因为close而关闭,若关闭,收到的值为对应数据的零值
for v:=range ch`可以一直循环直到ch关闭
buffered channels
发送时若buffer满了则会阻塞,受到时若buffer为空则阻塞
声明方法ch := make(chan type, capacity)
capacity为buffer的容量,意味着发送时未被收接收的数据存放数量一旦高于就会阻塞
可以通过len(eh)
获取还没有处理的数据,cap(ch)
获取容量
waitgroup
声明方法var wg sync.WaitGroup
用wg.Add(1)
添加计数,wg.Down()
减少计数
用wg.Wait()
等待直到计数归零
waitgroup作为实参传递的时候,需要加上&
防止函数内部传到的是副本而不是本身
workpool
处理的任务的方式
(并发1个)输入函数alloacate,将输入传递到’输入用channels’中
(并发n个)处理函数worker,从‘输入用channels’获取数据,处理后放在’输出用channels’
(并发1个)输出函数result,从’输出用channels’中获取数据用于输出
其中’输入用channels’和’输出用channels’为buffered pool,可以储存一定量的数据供处理
n个worker函数使得任务可以一次并发处理多个
个人认为好处可能是是当其中1个任务工作量太大的时候,依旧可以处理其它工作量较小的任务,而不至于被大任务完全阻塞?
select
1 | select { |
output1,output2是channel
类似switch,但是是用来处理channel的,当有channel收到数据的时候,就会执行该分支,在这之前将会阻塞
若同时收到数据的有多个,则随机选择一个分支执行
分支可以使用default,代表没有任何一个case的channel收到了数据则执行
个人认为存在default分支的select不会造成阻塞,因为要么收到了数据随机执行分支,要么没有收到执行default,不会有等待的时刻
select中default的使用可以避免死锁
空的select会造成永久阻塞
Mutex
临界区(Critical section)
每个线程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。
摘自百度百科
1 | mutex.Lock() |
mutex在Lock时会检测是否已经上锁了,如果有,则阻塞直到原先已经上锁的那边的锁的解除(即Unlock被执行)
可以防止因为多个Goroutines并发,同时修改同一个数据造成的数据错乱
和WaitGroup一样,传递Mutex变量需要使用&
来确保不同函数传入的是同一个而非拷贝,因为Mutex只有在同一个变量上锁的情况下才会阻塞
也可以使用channel达到与mutex相同的效果,令ch为capacity为1的buffered channel1
2
3ch<-true//存在数据,其它函数内的ch只能阻塞直到这个数据被接收
x = x+1
<-ch//接收ch内的数据,其它函数内的ch现在可以解除阻塞来传入数据了
“仿造”Class
因为go里并没有提供’构造函数’功能,我们只能声明一个函数,假装它是构造函数:
将struct用小写(比如说 a)设置为外部不可见,这样外部就不能直接声明变量来获得这个类型
构造一个函数(比如用Nwe命名),生成一个a类型,并且为其赋值,然后返回这个a类型
由于外部只能通过这个函数生成到这种类型的数据,这个函数将充当构造函数
Defer
在函数或者方法前面添加defer
关键词,可以让它在return之前才调用,但是保留当时参数的值,如1
2
3
4
5
6
7
8
9
10func printA(a int){
fmt.Println("PrintA:",a)
}
func print(){
a:=5
defer printA(a)
a=10
fmt.Println("Print:",a)
}
输出为1
2Print: 10
PrintA: 5
多个defer采用栈(LIFO)后进先出的顺序执行
可用于有多个return分支但是又希望在return前进行统一动作的时候使用
Error
自带有error接口,只要声明了Error()
函数的类型,就可以作为错误输出数据
此时Println()
函数会调用该数据的Error,输出其返回的stringerrors.New("错误信息")
也可以用来创建新的错误数据fmt.Errorf("错误信息,附带值:%d",10)
同样可以用来创建新的错误数据
通过类型断言来转化错误类型获得更多信息
比如说对于打开文件f,err:=os.Opem("path/")
,可以断言errif err, ok := err.(*os.PathError);ok
这样就可以用err.path
获取到路径信息
func panic(interface{})
可以用来输出那些可能会导致程序无法继续的错误
调用该函数后,调用者函数将会被中止,未处理的deder的函数将会被处理
然后以同样的方式中止(调用(调用(panic函数)的函数)的函数),逐级向外推进,直到推进到没有外层函数为止(即程序中止)
程序中止时,将输出调用panic函数的位置追踪。
可以在deder中调用func recover() interface{}
来中止panic向外推进的过程
例如在1
2
3
4
5func recoverName() {
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}
那么在调用defer recoverName()
的函数不再向外层传递panic的影响(但调用函数本身依旧受到影响)
这使得panic只限制于一个范围内,而非让整个程序终止
recover只限于恢复同一个goroutine内的函数,无法在一个goroutine下恢复另一个goroutine造成的panic
runtime error也是内建的panic,所以也可以通过recover恢复
recover会中止错误路径追踪,如果需要追踪,则要在recover的函数内添加debug.PrintStack()
函数来打印它
一级函数
(关于类型的分类资料)[http://rednaxelafx.iteye.com/blog/184199]
类型:规定了变量可以取的值得范围,以及该类型的值可以进行的操作。根据类型的值的可赋值状况,可以把类型分为三类:
1、一级的(first class)。该等级类型的值可以传给子程序作为参数,可以从子程序里返回,可以赋给变量。大多数程序设计语言里,整型、字符类型等简单类型都是一级的。
2、二级的(second class)。该等级类型的值可以传给子程序作为参数,但是不能从子程序里返回,也不能赋给变量。
3、三级的(third class)。该等级类型的值连作为参数传递也不行。
所以一级函数就是能作为参数传递的函数
允许闭包,允许匿名函数,允许函数作为参数或者返回值