GO 并发编程

1.goroutine

goroutine是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。

在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用
的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个

返回值会被丢弃。

并发执行实例

package main
import "fmt"
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
func main() {
for i := 0; i < 10; i++ {
go Add(i, i)
}
}

2.并发通信

Go语言提供的消息通信机制被称为channel

channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。Go语言对于网络方面也有非常完善的支持。

channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。如果对Unix管道有所了解的话,就不难理解channel,可以将其认为是一种类型安全的管道。

实例:

package main
import "fmt"
func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range(chs) {
<-ch
}
}

在这个例子中,我们定义了一个包含10个channel的数组(名为chs),并把数组中的每个channel分配给10个不同的goroutine。在每个goroutine的Add()函数完成后,我们通过ch <- 1语句向对应的channel中写入一个数据。在这个channel被读取前,这个操作是阻塞的。在所有的goroutine启动完成后,我们通过<-ch语句从10个channel中依次读取数据。在对应的channel写入数据前,这个操作也是阻塞的。这样,我们就用channel实现了类似锁的功能,进而保证了所有goroutine完成后主函数才返回。

channel的声明

var ch chan int

不带缓冲的channel 

ch :=     make(chan int)

带缓冲的channel 

ch :=     make(chan int, 10)

带缓冲的channel中的数据读取

for i := range ch {
fmt.Println("Received:", i)
}

向channel中写数据,向channel写入数据通常会导致程序阻塞,直到有其他goroutine从这个channel中读取数据。

ch <- value
读数据。如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止

value := <-ch

单向channel

var ch2 chan<- float64// ch2是单向channel,只用于写float64数据

var ch3 <-chan int // ch3是单向channel,只用于读取int数据

转成双向channel

ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一个单向的读取channel
ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel

select关键字

通过调用select()函数来监控一系列的文件句柄,一旦其中一个文件句柄发生了IO动作,该select()调用就会被返回。后来该机制也被用于实现高并发的Socket服务器程序。Go语言直接在语言级别支持select关键字,用于处理异步IO问题

实例:

select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
多线程执行计算:


type Vector []float64
// 分配给每个CPU的计算任务
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
for ; i < n; i++ {
v[i] += u.Op(v[i])
}
c <- 1 // 发信号告诉任务管理者我已经计算完成了
}
const NCPU = 16 // 假设总共有16核
func (v Vector) DoAll(u Vector) {
c := make(chan int, NCPU) // 用于接收每个CPU的任务完成信号
for i := 0; i < NCPU; i++ {
go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)
}
// 等待所有CPU的任务完成
for i := 0; i < NCPU; i++ {
<-c // 获取到一个数据,表示一个CPU计算完成了
}
// 到这里表示所有计算已经结束
}

同步锁的使用

Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。Mutex是最简单的一种锁类型,同时也比较暴力,当一个goroutine获得了Mutex后,其他goroutine就只能乖乖等到这个goroutine释放该Mutex。RWMutex相对友好些,是经典的单写多读模型。在读锁占用的情况下,会阻止写,但不阻止读,也就是多个goroutine可同时获取读锁(调用RLock()方法;而写锁(调用Lock()方法)会阻止任何其他goroutine(无论读和写)进来,整个锁相当于由该goroutine独占。从RWMutex的实现看,RWMutex类型其实组合了Mutex:

实例:

var l sync.Mutex
func foo() {
l.Lock()
defer l.Unlock()
//...
}


阅读更多

没有更多推荐了,返回首页