仅作java2go的简单记录,有错误烦请提出
合集整理:Java转Go的学习-合集
demo已上传git:https://gitee.com/yayako/go-learning.git
go中的并发
goroutine
协程
用户级线程,对内核透明,系统并不知道有协程的存在,完全由自己的程序进行调度。
在一个golang程序的主线程上可以起多个协程,golang中多协程可以实现并发或者并行。
当主线程执行完毕后即使协程没有执行完毕,程序也会退出。
goroutine
golang的一大特色:从语言层面原生支持协程,goroutine就是其表现形式。
goroutine默认占用内存远比线程少,os线程一般都有固定的栈内存(2mb左右),而一个goroutine占用内存非常小(2kb左右)。另一方面,多协程goroutine切换调度的开销远比线程调度要小。
基本使用
使用goroutine的方式很简单,直接在需要另起协程执行的方法前加上 go 关键字即可,以及,为了防止在协程因主线程停止而被动停止,需要使用协程计数器来保证“通讯”。相关操作实现都在sync包中。
一个简单的使用goroutine的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var wg sync.WaitGroup
func main() { wg.Add(1) go goroutineTest() wg.Wait() fmt.Println("main 退出") }
func goroutineTest() { defer wg.Done() }
|
其中 wg.Done()
源码如下,实际上就是计数器-1。
1 2 3
| func (wg *WaitGroup) Done() { wg.Add(-1) }
|
goroutine的异常处理
假设在协程执行的方法中出现了异常,则需要使用 defer+recover来捕获处理
但必须要注意执行 wg.Done()
,和Java的finally一定要释放锁是同一个道理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| var wg sync.WaitGroup
func main() { wg.Add(2) go errFunc() go healthyFunc() wg.Wait() }
func errFunc() { defer func() { if err := recover(); err != nil { fmt.Println("【errFunc】执行失败,异常已捕获") } wg.Done() }() var myMap map[int]int myMap[0] = 0 }
func healthyFunc() { defer wg.Done() time.Sleep(time.Millisecond * 50) fmt.Println("【healthyFunc】执行成功") }
|
控制台输出结果
1 2
| 【errFunc】执行失败,异常已捕获 【healthyFunc】执行成功
|
channel
channel(管道)是语言级别上提供的goroutine间的通讯方式,可以在多个goroutine之间传递消息,是一种通信机制。
go语言的并发模型是CSP(Conmunicating Sequential Process),提倡通过通信共享内存而不是通过共享内存实现通信。
channel在go中是一种特殊的类型,且是引用类型,遵循fifo的规则,保证收发数据的顺序,每一个管道都是一个具体类型的导管,声明时需要为其指定元素类型。
基本使用
通过 chan 关键字声明,通过 make 函数初始化
通过 <- 读取/写入数据
1 2 3 4 5 6 7 8 9 10
| channel := make(chan int, 2) channel <- 1 channel <- 2
fmt.Printf("值:%v,类型:%T,容量:%v,长度:%v\n", channel, channel, cap(channel), len(channel)) a := <-channel fmt.Println(a) fmt.Println(<-channel)
|
通过 for
遍历读取
1 2 3
| for i := 0; i < 2; i++ { fmt.Println(<-channel) }
|
但需要注意的是,如果通过 for range
遍历读取,则在遍历之前必须关闭通道,否则会出现死锁问题
1 2 3 4
| close(channel) for v := range channel { fmt.Println(v) }
|
单向管道
channel默认情况下是双向管道,可读可写,只写或只读的管道称为单向管道。
单向管道只需要在初始化时声明类型即可
1 2 3 4
| wch := make(chan<- int, 2)
rch := make(<-chan int, 2)
|
下面结合goroutine和channel实现并行读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| var wg sync.WaitGroup
func main() { ch := make(chan int, 10) wg.Add(2) go write(ch) go read(ch) wg.Wait() }
func write(ch chan int /* 可使用单向管道(只写):ch chan<- int */) { for i := 0; i < 10; i++ { ch <- i fmt.Printf("【写入】%v\n", i) time.Sleep(time.Millisecond * 50) } close(ch) wg.Done() }
func read(ch chan int /* 可使用单向管道(只读):ch <-chan int */) { for v := range ch { fmt.Printf("【读取】%v\n", v) time.Sleep(time.Millisecond * 50) } wg.Done() }
|
控制台输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 【写入】0 【读取】0 【写入】1 【读取】1 【写入】2 【读取】2 【写入】3 【读取】3 【写入】4 【读取】4 【写入】5 【读取】5 【写入】6 【读取】6 【写入】7 【读取】7 【写入】8 【读取】8 【写入】9 【读取】9
|
select多路复用
当我们需要同时从多个channel接收数据时,可以选择开启多个协程来完成,但这种方式的运行性能会差很多,可以进一步使用 select-case 来实现多路复用。
每个case对应一个channel的通信过程,select会一直等待,直到某个case的通信操作完成时,就会执行case分支内的语句。
假设有两个channel,分别写入了int和float64,现在要通过select实现同时从两个channel中读取数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func main() { intChan := make(chan int, 5) floatChan := make(chan float64, 5) for i := 0; i < 5; i++ { intChan <- i floatChan <- float64(i) + 0.1 } for { select { case v := <-intChan: fmt.Printf("【intChan】%v\n", v) time.Sleep(time.Millisecond * 50) case v := <-floatChan: fmt.Printf("【floatChan】%v\n", v) time.Sleep(time.Millisecond * 50) default: fmt.Println("【over】") break } } }
|
控制台输出结果
1 2 3 4 5 6 7 8 9 10 11
| 【floatChan】0.1 【floatChan】1.1 【intChan】0 【intChan】1 【floatChan】2.1 【intChan】2 【intChan】3 【floatChan】3.1 【floatChan】4.1 【intChan】4 【over】
|
lock
同样的,sync包中还提供了多种锁组件,如互斥锁、读写锁等。
以下是一个简单的互斥锁的实践,通过互斥锁保证共享资源 count
在并发情况下的的递增。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var wg sync.WaitGroup var mutex sync.Mutex
var count int
func main() { for i := 0; i < 20; i++ { wg.Add(1) go mutexTest() } go wg.Wait() }
func mutexTest() { mutex.Lock() count++ fmt.Println("count =", count) wg.Done() mutex.Unlock() }
|
获取cpu数量
go的runtime包中提供了多种对环境变量的操作,例如获取cpu核数
1 2
| cpuNum := runtime.NumCPU() fmt.Println("cpu数量", cpuNum)
|