GoLang 高并发 Goroutine(一)

GoLang 高并发 Goroutine(一)

  • 并发和并行
  • Goroutine
    • goroutine 是如何工作的
    • fork-join并发模型
    • 闭包
      • 例1
      • 例2

并发和并行

并发和并行:并发属于代码,并行属于一个运行的程序;具体是我们并没有编写并行的代码,而是期待在运行时能够并行的并发代码

Goroutine

goroutine 是一个并发函数(并不是并行

func main(){
	go sayHello()
}
func sayHello(){
	fmt.Println("你好")
}

/*
使用匿名函数
func main(){
	go func(){
		fmt.Println("你好")
	}()
}
*/

goroutine 是如何工作的

goroutine 既不是os线程,也不是由语言运行时管理的线程,它是一个协程(协程是一个非抢占式的简单并发子goroutine(函数、闭包或方法))。 goroutine没有定义自己的暂停方法和在运行点,Go语言在运行时会观察goroutine的运行状态,并在它们阻塞时自动挂起它们,然后在它们不阻塞时恢复它们。

fork-join并发模型

fork指在程序中的任意一点,可以将执行的子分支与其父节点同时运行;join在将来的某个时候,这些并发的执行分支将会合并在一起。

func main(){
	sayHello := func(){
		fmt.Println("你好")
	}
	go sayHello()
}

在这个例子中,没有join点,执行sayHello()的goroutine将在未来某个不确定的时间退出,而程序的其余部分将会继续执行;因为不确定sayHello函数是否会执行,具体而言goroutine将会被创建,但是可能在还没有执行,此时main goroutine已经退出结果是:可能看不到在屏幕上打印出 你好。有一种比较笨且没有任何实际意义的方法,就是在创建goroutine之后,使用time.Sleep让main goroutine睡眠一会,这种方式并不能保证结果的正确性,只是提供了一种让结果趋近于正确的方式。

因此要创建一个join使得main goroutine 和 sayHello goroutine 同步。

var wg sync.WaitGroup
sayHello := func(){
	defer wg.Done()
	fmt.Println("你好")
}
wg.Add(1)
go sayHello()
wg.Wait() // 连接点,main goroutine等待直到所有wg Done()

闭包

**闭包** 可以从创建它们的作用域获取变量;思考:如果我们在goroutine上运行一个闭包,那么闭包是在这些变量的副本上运行,还是原值的引用?

例1

var wg sync.WaitGroup
strs := "你好"
wg.Add(1)
go func(){
	defer wg.Done()
	strs = "哈哈" //修改了变量strs的值
}()
wg.Wait()
fmt.Println(strs) //哈哈

goroutine在它们所创建的相同地址空间内执行

例2

var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
	wg.Add(1)
	go func(){
		defer wg.Done()
		fmt.Println(str)
	}()
}

/*
你好3
你好3
你好3
*/

该闭包在使用str的时候,字符串的迭代已经结束了。因为goroutine可能在任何时间点运行,所以不确定在goroutine中会打印什么值。还有一个问题是,如果在迭代结束后,str的值还在不在内存中,goroutine还能不能引用已经超出范围的内容。这涉及到Go语言运行时会对变量str的值的引用仍然保留,有内存转移到堆,所以goroutine仍然可以继续访问它。

var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
	wg.Add(1)
	go func(s string){
		defer wg.Done()
		fmt.Println(s) //构建一个str的副本
	}(str)
}
/*
你好3
你好1
你好2
*/

由于多个goroutine可以在同一个地址空间上运行,所以我们要考虑资源竞争(同步问题)。

你可能感兴趣的:(并发)