给网站做seo的价格,域名购买后还要解析吗,wordpress开源博客系统最新版,容桂网站制作信息进程、线程以及并行、并发
关于进程和线程 进程#xff08;Process#xff09;就是程序在操作系统中的一次执行过程#xff0c;是系统进行资源分配和调度的基本单位#xff0c;进程是一个动态概念#xff0c;是程序在执行过程中分配和管理资源的基本单位#xff0c;每一…进程、线程以及并行、并发
关于进程和线程 进程Process就是程序在操作系统中的一次执行过程是系统进行资源分配和调度的基本单位进程是一个动态概念是程序在执行过程中分配和管理资源的基本单位每一个进程都有一个自己的地址空间。 一个进程至少有 5 种基本状态它们是初始态执行态等待状态就绪状态终止状态。 通俗的讲进程就是一个正在执行的程序。 线程 是进程的一个执行实例是程序执行的最小单元它是比进程更小的能独立运行的基本单位 一个进程可以创建多个线程同一个进程中的多个线程可以并发执行一个程序要运行的话至少有一个进程。 关于并行和并发 并发多个线程同时竞争一个位置竞争到的才可以执行每一个时间段只有一个线程在执行。 并行多个线程可以同时执行每一个时间段可以有多个线程同时执行。 总结而言 多线程程序在单核 CPU 上面运行就是并发 多线程程序在多核 CUP 上运行就是并行。 如果线程数大于 CPU 核数则多线程程序在多个 CPU 上面运行既有并行又有并发 Golang 中的协程goroutine以及主线程 golang 中的主线程可以理解为线程/也可以理解为进程在一个 Golang 程序的主线程上可以起多个协程。 Golang 中多协程可以实现并行或者并发。 协程可以理解为用户级线程这是 对内核透明的也就是系统并不知道有协程的存在 是完全由用户自己的程序进行调度的。Golang 的一大特色就是从语言层面原生支持协程在函数或者方法前面加 go 关键字就可创建一个协程。可以说 Golang 中的协程就是goroutine 。 Golang 中的多协程有点类似其他语言中的多线程。 多协程和多线程Golang 中每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少。 OS 线程操作系统线程一般都有固定的栈内存通常为 2MB 左右,一个 goroutine (协程) 占用内存非常小只有 2KB 左右多协程 goroutine 切换调度开销方面远比线程要少。 这也是为什么越来越多的大公司使用 Golang 的原因之一。 Goroutine 的使用 案例如下 package mainimport(fmttime
)// 在主线程中也每隔10毫输出卫宫士郎, 输出2次后退出程序
// 要求主线程和goroutine同时执行
func test() {for i : 0; i 10; i {fmt.Println(test() 测试专用..........)time.Sleep(time.Millisecond * 100)}
}func main(){go test()for i : 1; i 2; i {fmt.Println(main () 卫宫士郎)time.Sleep(time.Millisecond*10)}} 暴露出一个问题主线程执行完毕后即使协程没有执行完毕 所以我们对代码进行改造可以让主线程和协程并行的同时主线程执行完毕还不会同时带领协程退出运行。 注意: 1、主线程执行完毕后即使协程没有执行完毕程序也会退出 2、协程可以在主线程没有执行完毕前提前退出协程是否执行完毕不会影响主线程的执行为了保证我们的程序可以顺利执行我们想让协程执行完毕后在执行主进程退出。 这个时候我们可以使用sync.WaitGroup 等待协程执行完毕 sync.WaitGroup sync.WaitGroup 可以实现主线程等待协程执行完毕。 package mainimport(fmttimesync
)// 在主线程中也每隔10毫输出卫宫士郎, 输出2次后退出程序
// 要求主线程和goroutine同时执行
//主线程退出后所有的协程无论有没有执行完毕都会退出所以我们在主进程中可以通过WaitGroup等待协程执行完毕
var sw sync.WaitGroupfunc test() {for i : 0; i 10; i {fmt.Println(test() 测试专用..........)time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func main(){sw.Add(1) //协程计数器1go test()//表示开启一个协程for i : 1; i 2; i {fmt.Println(main () 卫宫士郎)time.Sleep(time.Millisecond*10)}sw.Wait() //等待协程执行完毕...fmt.Println(主线程执行完毕、、、、、、)
} 启动多个 Goroutine package mainimport(fmttimesync
)// 多个协程Goroutine启动var sw sync.WaitGroupfunc test0() {for i : 0; i 5; i {fmt.Println(test0() 测试专用..........)time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func test1() {for i : 0; i 5; i {fmt.Println(test1() 测试专用..........)time.Sleep(time.Millisecond * 100)}sw.Done() //协程计数器-1
}func main(){sw.Add(1) //协程计数器1go test0()//表示开启一个协程sw.Add(1)//协程计数器1go test1()//表示开启一个协程for i : 1; i 2; i {fmt.Println(main () 卫宫士郎)time.Sleep(time.Millisecond*10)}sw.Wait() //等待协程执行完毕...fmt.Println(主线程执行完毕、、、、、、)
} 多次执行上面的代码会发现每次打印的数字的顺序都不一致。这是因为 10 个 goroutine 是并发执行的而 goroutine 的调度是随机的。 设置 Golang 并行运行的时候占用的 cup 数量 Go 运行时的调度器使用 GOMAXPROCS 参数来确定需要使用多少个 OS 线程来同时执行 Go 代码。默认值是机器上的 CPU 核心数。例如在一个 8 核心的机器上调度器会把 Go 代码同时调度到 8 个 OS 线程上。 Go 语言中可以通过 runtime.GOMAXPROCS() 函数设置当前程序并发时占用的 CPU 逻辑核心数。 Go1.5 版本之前默认使用的是单核心执行。 Go1.5 版本之后默认使用全部的 CPU 逻辑核心数。 package mainimport (fmtruntime
)func main() {//获取当前计算机上面的Cup个数cpuNum : runtime.NumCPU()fmt.Println(cpuNum, cpuNum)//可以自己设置使用多个cpuruntime.GOMAXPROCS(cpuNum - 1)fmt.Println(设置完成)
}//cpuNum 8
//设置完成来求一个素数的操作如下 package mainimport (fmttime
)func main() {start : time.Now().Unix()fmt.Println(start)for num : 2; num 10; num {var flag truefor i : 2; i num; i {if num%i 0 {flag falsebreak}}if flag {fmt.Println(num, 是素数)}}end : time.Now().Unix()fmt.Println(end)fmt.Println(end-start) }goroutine for循环实现 package mainimport (fmtsynctime
)//需求要统计1-120000的数字中那些是素数goroutine for循环实现/*
1 协程 统计 1-300002 协程 统计 30001-600003 协程 统计 60001-900004 协程 统计 90001-120000// start:(n-1)*300001 end:n*30000
*/
var wg sync.WaitGroupfunc test(n int) {for num : (n-1)*30000 1; num n*30000; num {if num 1 {var flag truefor i : 2; i num; i {if num%i 0 {flag falsebreak}}if flag {// fmt.Println(num, 是素数)}}}wg.Done()
}func main() {for i : 1; i 4; i {wg.Add(1)go test(i)}wg.Wait()fmt.Println(执行完毕)}Channel 管道 channel 单纯地将函数并发执行是没有意义的。 函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性必须使用互斥量对内存进行加锁这种做法势必造成性能问题。 Go语言的并发模型是CSPCommunicating Sequential Processes提倡通过通信共享内存而不是通过共享内存而实现通信。 如果说goroutine是Go程序并发的执行体channel就是它们之间的连接。 channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。 Go 语言中的通道channel是一种特殊的类型。通道像一个传送带或者队列总是遵循先入先出First In First Out的规则保证收发数据的顺序。每一个通道都是一个具体类型的导管也就是声明channel的时候需要为其指定元素类型。 channel类型 channel是一种类型一种引用类型。声明通道类型的格式如下 var 变量 chan 元素类型 举几个例子 var ch1 chan int // 声明一个传递整型的通道var ch2 chan bool // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道 创建channel
通道是引用类型通道类型的空值是nil。
var ch chan int
fmt.Println(ch) // nil
package mainimport fmtfunc main() {ch1 : make(chan int ,4)ch1- 1ch1- 2ch1- 3ch2 : ch1ch2-4-ch1-ch1-ch1d: -ch1fmt.Println(d)
}//4
副本ch2的值添加后取出ch1的值改变了 声明的通道后需要使用make函数初始化之后才能使用。
创建channel的格式如下 make(chan 元素类型, [缓冲大小])
channel的缓冲大小是可选的。
举几个例子
//创建一个能存储 10 个 int 类型数据的管道
ch1 : make(chan int, 10)
//创建一个能存储 4 个 bool 类型数据的管道
ch2 : make(chan bool, 4)
//创建一个能存储 3 个[]int 切片类型数据的管道
ch3 : make(chan []int, 3)
package mainimport fmtfunc main() {//创建channelch : make(chan int, 3)//2、给管道里面存储数据ch - 12ch - 33ch - 3234//获取管道里面的内容a : -chfmt.Println(a) //12-ch //从管道里面取值 //33c : -chfmt.Println(c) //3234ch - 1ch - 22//打印管道的长度和容量fmt.Printf(值%v 容量%v 长度%v\n, ch, cap(ch), len(ch))
}已经消费了的就相当于没有再添加的从新算 channel操作
通道有发送send、接收(receive和关闭close三种操作。
发送和接收都使用-符号。
现在我们先使用以下语句定义一个通道
ch : make(chan int)
发送
将一个值发送到通道中。
ch - 10 // 把10发送到ch中
接收
从一个通道中接收值。
x : - ch // 从ch中接收值并赋值给变量x
-ch // 从ch中接收值忽略结果
关闭
我们通过调用内置的close函数来关闭通道。 close(ch) 关于关闭通道需要注意的事情是只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的它和关闭文件是不一样的在结束操作之后关闭文件是必须要做的但关闭通道不是必须的。
关闭后的通道有以下特点 1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。
管道阻塞
无缓冲的通道 如果创建管道的时候没有指定容量那么我们可以叫这个管道为无缓冲的管道 无缓冲的管道又称为阻塞的管道。我们来看一下下面的代码 package main
import (fmt
)func main() {ch : make(chan int)ch - 123fmt.Println(传递成功......)
} 上面这段代码能够通过编译但是执行的时候会出现以下错误 fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:8 0x31
exit status 2 为什么会出现死锁 因为我们使用ch : make(chan int)创建的是无缓冲的通道无缓冲的通道只有在有接收值的时候才能发送值。小区没代收快递点需要快递小哥直接送到手上 上面的代码会阻塞在ch - i这一行代码形成死锁 因为我们使用ch : make(chan int)创建的是无缓冲的通道无缓冲的通道只有在有人接收值的时候才能发送值。 上面的代码会阻塞在ch - 123这一行代码形成死锁那如何解决这个问题呢 一种方法是启用一个goroutine去接收值例如 func recv(c chan int) {ret : -cfmt.Println(接收成功, ret)
}
func main() {ch : make(chan int)go recv(ch) // 启用goroutine从通道接收值ch - 10fmt.Println(发送成功)
} 无缓冲通道上的发送操作会阻塞直到另一个goroutine在该通道上执行接收操作这时值才能发送成功两个goroutine将继续执行。 有缓冲的通道 解决上面问题的方法还有一种就是使用有缓冲区的通道。 package main
import (fmt
)// func recover(ch chan int){
// rec : - ch
// fmt.Println(接收成功,rec)
// }func main() {ch : make(chan int,1)// go recover(ch)ch - 123fmt.Println(传递成功......)
} 只要通道的容量大于零那么该通道就是有缓冲的通道通道的容量表示通道中能存放元素的数量。(小区快递格子就一个你取走了别人能再放) 循环遍历管道数据
循环的话我们就会提到for但是for有两种循环形式
for range 和 for 用两种方式来操作
for range循环遍历管道的值 ,注意管道没有key
package mainimport fmtfunc main() {ch1 : make(chan int,5)for i : 1; i 5; i {ch1 - i}for v : range ch1 {fmt.Println(v)}}我们发现虽然可以正常编译运行但是会出现如下情况
1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:14 0xb4
exit status 2
这样也会产生死锁使用for range遍历通道当通道被关闭的时候就会退出for range,如果没有关闭管道就会报错fatal error: all goroutines are asleep - deadlock! 如果通过for range循环的方式来从管道取数据在插入数据的时候一定要close package main
import (fmt
)func main() {var ch1 make(chan int, 5)for i : 1; i 5; i {ch1 - i}close(ch1) //关闭管道//for range循环遍历管道的值 ,注意管道没有keyfor v : range ch1 {fmt.Println(v)}
} 通过内置的close()函数关闭channel如果你的管道不往里存值或者取值的时候一定记得关闭管道 第二种方法
package main
import (fmt
)func main() {//通过for循环遍历管道的时候管道可以不关闭var ch2 make(chan int, 5)for i : 1; i 5; i {ch2 - i}for j : 0; j 5; j {fmt.Println(-ch2)}
}
并发安全和锁
有时候在Go代码中可能会存在多个goroutine同时操作一个资源临界区这种情况会发生竞态问题数据竞态。
互斥锁 互斥锁是传统并发编程中对共享资源进行访问控制的主要手段它由标准库 sync 中的 Mutex结构体类型表示。sync.Mutex 类型只有两个公开的指针方法Lock 和 Unlock。Lock 锁定当前的共享资源Unlock 进行解锁 package mainimport (fmtsynctime
)var count 0
var sw sync.WaitGroupvar mutex sync.Mutexfunc test() {mutex.Lock()countfmt.Println(the count is : , count)time.Sleep(time.Millisecond)mutex.Unlock()sw.Done()
}func main() {for r : 0; r 20; r { //开启20个协程来进行这个操作wg.Add(1)go test()}sw.Wait()}使用互斥锁能够保证同一时间有且只有一个 goroutine 进入临界区其他的 goroutine 则在等 待锁当互斥锁释放后等待的 goroutine 才可以获取锁进入临界区多个 goroutine 同时等 待一个锁时唤醒的策略是随机的。 虽然使用互斥锁能解决资源争夺问题但是并不完美通过全局变量加锁同步来实现通讯 并不利于多个协程对全局变量的读写操作。这个时候我们也可以通过另一种方式来实现上面 的功能管道(Channel) 读写互斥锁 互斥锁是完全互斥的但是有很多实际的场景下是读多写少的当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的这种场景下使用读写锁是更好的一种选择。 读写锁在Go语言中使用sync包中的RWMutex类型。 读写锁分为两种读锁和写锁。当一个goroutine获取读锁之后其他的goroutine如果是获取读锁会继续获得锁如果是获取写锁就会等待 当一个goroutine获取写锁之后其他的goroutine无论是获取读锁还是写锁都会等待。 package mainimport(fmtsynctime
)var (x int64wg sync.WaitGrouplock sync.Mutexrwlock sync.RWMutex
)func write() {// lock.Lock() // 加互斥锁rwlock.Lock() // 加写锁x x 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒fmt.Println(进行写操作)rwlock.Unlock() // 解写锁// lock.Unlock() // 解互斥锁wg.Done()
}func read() {// lock.Lock() // 加互斥锁rwlock.RLock() // 加读锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒fmt.Println(进行读操作)rwlock.RUnlock() // 解读锁// lock.Unlock() // 解互斥锁wg.Done()
}func main() {for i : 0; i 3; i {wg.Add(1)go write()}for i : 0; i 10; i {wg.Add(1)go read()}wg.Wait()}/**/ 总结 当我们对一个不会变化的数据只做“读”操作的话是不存在资源竞争的问题的。 因为数据是不变的不管怎么读取多少 goroutine 同时读取都是可以的。 所以问题不是出在“读”上主要是修改也就是“写”。 修改的数据要同步这样其他goroutine 才可以感知到。 所以真正的互斥应该是读取和修改、修改和修改之间读和读是没有互斥操作的必要的。 因此衍生出另外一种锁叫做读写锁。 读写锁可以让 多个读操作并发同时读取 但是 对于写操作是完全互斥 的。 也就是说当一个 goroutine 进行写操作的时候其他 goroutine 既不能进行读操作也不能进行写操作 Goroutine 结合 Channel 管道
需求 1 定义两个方法一个方法给管道里面写数据一个给管道里面读取数据。要求同步进行。 1 、开启一个 fn1 的的协程给向管道 inChan 中写入 100 条数据 2 、开启一个 fn2 的协程读取 inChan 中写入的数据 3 、注意 fn1 和 fn2 同时操作一个管道 4 、主线程必须等待操作完成后才可以退出 package mainimport (fmtsynctime
)
//这是一个无缓存通道案例
//定义sync等待协程完毕
var wg sync.WaitGroupfunc fn1(intChan chan int) {for i : 0; i 10; i {intChan - i 1fmt.Println(写入数据, i1)time.Sleep(time.Millisecond * 100)}close(intChan) //写入操作完毕关闭写入的协程wg.Done()
}
func fn2(intChan chan int) {for v : range intChan { //通道回显只有一个值fmt.Printf(读到数据%v\n, v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan : make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println(读取完毕...)
}需求 2
goroutine 结合 channel 实现统计 1-120 的数字中那些是素数 package mainimport(fmtsync
)var sw sync.WaitGroup
//向 intChan放入 1-120个数创建协程
func putNum(intChan chan int ){for i : 0; i 120; i {intChan - i}close(intChan)sw.Done()
}// 从 intChan取出数据并判断是否为素数,如果是就把得到的素数放在primeChanfunc primeNum(intChan chan int,primeChan chan int, exitChan chan bool ){for num : range intChan {var flag truefor i : 2; i num; i {if num%i 0 {flag falsebreak}}if flag {primeChan - num //num是素数}
}//要关闭 primeChan// close(primeChan) //如果一个channel关闭了就没法给这个channel发送数据了//什么时候关闭primeChan?//给exitChan里面放入一条数据exitChan - true sw.Done()}//printPrime打印素数的方法
func printPrime(primeChan chan int) {for v : range primeChan {fmt.Println(v)}sw.Done()
}func main(){intChan : make(chan int,1000) //在intchan中放入数字primeChan : make(chan int,1000) //从 intChan取出数据判断是否是素数exitChan : make(chan bool ,20) //标识primeChan close,内部数据满足设定的缓存数量就关闭//存放数字的协程sw.Add(1)go putNum(intChan)//统计素数的协程for i : 0; i 20; i { //你要开启几个primechan的协程就写几个对应的exitchan要一致sw.Add(1)go primeNum(intChan ,primeChan , exitChan )}//打印素数的协程sw.Add(1)go printPrime(primeChan)//判断exitChan是否存满值sw.Add(1)go func() {for i : 0; i 20; i {-exitChan}close(primeChan) //关闭primeChansw.Done()}()sw.Wait()fmt.Println(执行完毕....)}
单向管道 有的时候我们会将管道作为参数在多个任务函数间传递很多时候我们在不同的任务函数中使用管道都会对其进行限制比如限制管道在函数中只能发送或只能接收。 案例如下 package mainimport fmt//单向管道
func main() {// 1、在默认情况下下管道是双向ch : make(chan int, 2)ch - 1ch - 2a : -chb : -chfmt.Println(a, b) //1,2// 2、管道声明为只写ch1 : make(chan- int, 2)ch1 - 10ch1 - 12// -ch1 //receive from send-only type chan- int// 3、管道声明为只读ch2 : make(-chan int, 2)ch2 - 3c : -ch2fmt.Println(c) //.\main.go:25:2: invalid operation: cannot send to receive-only channel ch2 (variable of type -chan int)}修改之前的案例如下
package mainimport (fmtsynctime
)
//这是一个无缓存通道案例
//定义sync等待协程完毕
var wg sync.WaitGroupfunc fn1(intChan chan- int) {for i : 0; i 10; i {intChan - i 1fmt.Println(写入数据, i1)time.Sleep(time.Millisecond * 100)}close(intChan) //写入操作完毕关闭写入的协程wg.Done()
}
func fn2(intChan -chan int) {for v : range intChan { //通道回显只有一个值fmt.Printf(读到数据%v\n, v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan : make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println(读取完毕...)
}/*
写入数据 1
读到数据1
写入数据 2
读到数据2
写入数据 3
读到数据3
写入数据 4
读到数据4
写入数据 5
读到数据5
写入数据 6
读到数据6
写入数据 7
读到数据7
写入数据 8
读到数据8
写入数据 9
读到数据9
写入数据 10
读到数据10
读取完毕...
*/select 多路复用
在某些场景下我们需要同时从多个通道接收数据,这个时候就可以用到golang中给我们提供的select多路复用
如果只想在main方法内进行就可以用这个方法其他的就是定义协程了 使用select来获取channel里面的数据的时候不需要关闭channel package mainimport(fmttime
)func main(){
// 在某些场景下我们需要同时从多个通道接收数据,这个时候就可以用到golang中给我们提供的select多路复用//如果只想在main方法内进行就可以用这个方法其他的就是定义协程了//1.定义一个管道 10个数据int
intoChan : make(chan int ,10)
for i : 0; i 10; i {intoChan - i
}
//2.定义一个管道 5个数据string
stringChan : make(chan string,5)
for i : 0; i 5; i {stringChan - 卫宫士郎
}
//定义一个for的无限循环
for{select{case value : - intoChan:fmt.Printf(从 intChan 读取的数据%d\n, value)case value : -stringChan:fmt.Printf(从 stringChan 读取的数据%v\n, value)time.Sleep(time.Millisecond * 50)default:fmt.Printf(数据获取完毕)return //注意退出...}
}}/*
从 stringChan 读取的数据卫宫士郎
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据0
从 intChan 读取的数据1
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据2
从 intChan 读取的数据3
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据4
从 stringChan 读取的数据卫宫士郎
从 intChan 读取的数据5
从 intChan 读取的数据6
从 intChan 读取的数据7
从 intChan 读取的数据8
从 intChan 读取的数据9
数据获取完毕*/ select 的使用类似于 switch 语句它有一系列 case 分支和一个默认的分支。每个 case 会对 应一个管道的通信接收或发送过程。 select 会一直等待直到某个 case 的通信操作完成 时就会执行 case 分支对应的语句。 Goroutine Recover 解决协程中出现的 Panic defer recover 延迟执行定义的func自执行函数出现问题就交给defer其他的协程还可以继续进行 package mainimport (fmttime
)//函数
func test0() {for i : 0; i 10; i {time.Sleep(time.Millisecond * 50)fmt.Println(远坂凛)}
}//函数
func test1() {//这里我们可以使用defer recover //延迟执行定义的func自执行函数出现问题就交给defer//其他的协程还可以继续进行defer func() {//捕获test抛出的panicif err : recover(); err ! nil {fmt.Println(test1() 发生错误, err)}}()//定义了一个mapvar myMap map[int]stringmyMap[0] golang //error}func main() {go test0()go test1()//防止主进程退出这里使用time.Sleep演示搭建也可以用sync.WaitGrouptime.Sleep(time.Second)
}注意调用recover()来捕获 goroutine 恐慌只在一个defer函数内部有用否则该函数将返回nil并且没有其他作用。这是因为defer函数也是在周围函数恐慌时执行的。 在 Go 中panic是一个停止普通流程的内置函数: func main() {fmt.Println(a)panic(foo)fmt.Println(b)
} 该代码打印a然后在打印b之前停止: a
panic: foogoroutine 1 [running]:
main.main()main.go:7 0xb3 一旦恐慌被触发它将继续在调用栈中向上运行直到当前的 goroutine 返回或者panic被recover捕获: func main() {defer func() { // ❶if r : recover(); r ! nil {fmt.Println(recover, r)}}()f() // ❷
}func f() {fmt.Println(a)panic(foo)fmt.Println(b)
} ❶ 延迟闭包内调用recover ❷ 调用ff恐慌。这种恐慌被前面的recover所抓住。 在f函数中一旦panic被调用就停止当前函数的执行并向上调用栈:main。在main中因为恐慌是由recover引起的所以并不停止 goroutine: a
recover foo