GO语言:第六节

73 阅读4分钟
//竞态

/*
竞态指多个goroutine按某些交错顺序执行时程序无法给出正确结果。
因为我们无法知道一个goroutine中的事件x和另一个goroutine中的事件y
的执行先后顺序。它们是并发的。

package main

import (
	"fmt"
	"time"
)

var myMoeny int

func give(money int) {
	myMoeny += money
}

func look() int {
	return myMoeny
}

func main() {

	go func() {
		give(100)
		fmt.Println("1:", look())
	}()

	go func() {
		give(200)
		fmt.Println("2:", look())
	}()

	time.Sleep(time.Second * 2)

}
这个时候由于两次存钱同时进行(不知道先后),有可能就出现
1:300 , 2:200的情况。但这并不是我们的初衷,我们希望的是
1:100,2:300的情况。

上面的例子中出现了数据竞态的情况:
两个goroutine并发读写一个变量并且至少其中一个是写入时。

根据数据竞态的定义我们有如下解决办法:
1. 在其他goroutine创建之前,数据已经初始且不在修改,
    这样每个gouroutine都只读,就不会出现数据竞态。
    
2. 避免从多个goroutine访问同一个变量。
    由于其他goroutine无法直接访问变量,它们需要使用
    通道来发送查询请求或者更新变量。(即不要通过共享
    内存来通信,通过通信来共享内存)。
    package main

import (
	"fmt"
	"time"
)

var giveMoney = make(chan int)
var recvMoney = make(chan int)

func give(money int) {
	giveMoney <-money
}

func look() int {
	return <-recvMoney
}

func teller() {
	var myMoney int

	for {
		select {
			case m := <-giveMoney:
				myMoney += m
			case recvMoney <- myMoney:
		}
	}
}

func main() {

	go teller()

	give(100)
	fmt.Println("1:", look())

	give(200)
	fmt.Println("2:", look())

	time.Sleep(time.Second * 2)

}

3. 运行多个gotoutine访问一个变量,但同一时间只能有一个访问。
互斥锁:sync.Mutex
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	mu sync.Mutex //保护共享变量myMoney
	myMoney int
)

func give(money int) {
	mu.Lock()
	defer mu.Unlock()

	myMoney += money
}

func look() int {
	mu.Lock()
	defer mu.Unlock()

	return myMoney
}


func main() {

	go func() {
		give(100)
		fmt.Println("1:", look())
	}()

	go func() {
		give(200)
		fmt.Println("2:", look())
	}()

	time.Sleep(time.Microsecond * 1000)
}
通过Lock方法获取一个互斥锁,如果其他goroutine取走互斥锁,那么操作
会一直阻塞到其他goroutine调用Unlock之后。


读写互斥锁:sync.RWMutex
它允许读操作并发执行,写操作只能独享访问权限
var (
	mu sync.RWMutex //保护共享变量myMoney
	myMoney int
)

func give(money int) {
	mu.Lock() //写锁
	defer mu.Unlock()

	myMoney += money
}

func look() int {
	mu.RLock()//读锁
	defer mu.RUnlock()

	return myMoney
}
sync.RWMutex在符锁竞争激烈的时候才有优势,不激烈时比普通互斥锁慢。
*/ 
//goroutine与线程

/*
goroutine并不等同于操作系统中的线程
1.操作系统中每个线程都有一个固定大小的栈内存,用来保护正在执行或者
    临时暂停的函数中的局部变量。
  goroutine的栈时可增长的,最开始时很小,最大可到1GB
2.OS线程由OS内核调度。每隔几毫秒,一个硬件时钟中断发到CPU,CPU调用
    一个叫调度器的内核函数。
  Go运行时包含一个自己的调度器,它可以调m个进程到n个OS线程。Go调度器
      不是由硬件时钟定期触发的,是由Go语言结构出发的。消耗成本更低。
3.Go调度器使用GOMAXPROCS参数确定使用多少个OS线程来同时执行Go代码。
    默认值是机器上的CPU数量。
4.goroutine没有标识符。OS线程都有独特的标识符。
*/
//反射机制

/*
反射机制:在编译时不知道类型的情况下,可更新变量,在运行时查看值,
调用方法以及直接对它们的布局进行操作。

一个熟悉的例子就是fmt.Printf它可以输出任意类型的任意值,甚至是用户
自定义的类型。

反射功能由reflect包提供,它定义了两个重要的类型:Type和Value。
Type表示Go语言的一个类型,它是一个有很多方法的接口,这些方法
可以用来识别类型以及透视类型的组成部分。
而reflect.Value可以包含一个任意类型的值。
reflect.Value和interface{}都可以包含任意的值。
二者的区别是空接口隐藏了值的布局信息,内置操作和相关方法,所以除非
我们最大它的动态类型,否则我们对值能作的事情很少。而Value有很多方法
分析值而不用知道它的类型。
*/