(五) Go特性

goroutine


goroutine 是由 Go 运行时环境管理的轻量级线程。

 go f(x, y, z)

 开启一个新的 goroutine 执行

f(x, y, z)

 f, x, y 和 z 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 f。

goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync提供了方法,不过在 Go 中并不经常用到,因为有其他的方法。

goroutine 使用:

func Show(text string) {
	time.Sleep(time.Millisecond)
	fmt.Println(text)
}

func main() {
	go Show("vim - go")
	Show("Welcome QuestionMark!")
}

 

channel


 channel 是有类型的管道,可以用channel操作符 <- 对其发送或者接收值

("<-" 箭头 就是数据流的方向。)

与 map 和 slice 一样。channel 使用前必须用 make 创建

func Show(ch chan int) {
	for {
		v, ok := <-ch
		if !ok {
			return
		}
		fmt.Println("v := ", v)
	}
}

func main() {
	ch := make(chan int)
	go Show(ch)
	ch <- 1
	ch <- 2
	ch <- 3
	time.Sleep(1000 * time.Millisecond)
	close(ch)
	fmt.Println("Complate")
}

/*
    执行结果:
    v := 1
    v := 2
    v := 3
    Complete  
*/

 默认情况下,在另一端准备好之前,发送和接收都会阻塞,这使得 goroutine 可以在没有明确锁或竞态变量的情况下进行同步。

 注意 channel 的接收也是按从右向左的顺序来的 符合编程执行顺序

num, num2 := <-ch, <-ch

 所以 num2 接收到的值 一定在 num 的前面 从右向左看。

 

 channel 也是可以带缓冲的。为 make 提供的第二参数来声明缓冲长度 并且 初始化一个缓冲

ch := make(chan int, 100)

 向带缓冲的 chanel 发送数据的时候,只有在缓冲区满的时候才会阻塞。而当缓冲区为空的时候接收操作会阻塞。

func Show(ch chan int) {
	for {
		v, ok := <-ch
		if !ok {
			return
		}
		fmt.Println("v := ", v)
		time.Sleep(2000 * time.Millisecond)
	}
}

func main() {
	ch := make(chan int, 1)
	go Show(ch)
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch)
	fmt.Println("Complate")
}

 

channel 配合 range 使用

func Show(ch chan int) {
	for v := range ch {
		fmt.Println("v := ", v)
		time.Sleep(2000 * time.Millisecond)
	}
}

func main() {
	ch := make(chan int, 1)
	go Show(ch)
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch)
	fmt.Println("Complate")
}

在循环中 使用 range 'channel' 会不断从 'channel' 中接收值,直到它被关闭 (close) 。

只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic 。

channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range。

关闭一个 channel 用 close(channel)。

 

channel 配合 select 使用

func Show(ch chan string, quit chan int) {
	for {
		select {
		case v := <-ch:
			fmt.Println("ch : ", v)
		case <-quit:
			fmt.Println("quit")
			return
		default:
			fmt.Println("No received from the channel value")
			time.Sleep(1000 * time.Millisecond)
		}
	}

}

func main() {
	ch := make(chan string)
	quit := make(chan int)
	go Show(ch, quit)
	ch <- "hello"
	ch <- "QuestionMark"
	quit <- 1
	time.Sleep(1000 * time.Millisecond)
}

 select 语句使得一个 goroutine 在多个通讯操作上等待。

select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。但多个都准备好的时候,会随机选择一个。

select 中的其他条件分支都没有准备好的时候, default 分支会被执行。

为了非阻塞的发送或者接收,可使用 default 分支。

 

Sync.Mutex


 如果在使用 channel 的同时我们需要这些 channel 获取使用共享数据

(比如食堂的故事/比如澡堂的故事/就是各种需要排队的故事咯······)

也就是说我们想保证 channel 拿到的共享数据并且在使用完共享数据的这个过程中 要保证这个共享数据的值是唯一的且不被改变影响的。

这个概念叫做 互斥,通常使用 _互斥锁_(mutex)_ 来提供这个限制。

Go 标准库中提供了 sync_Mutex 类型及两个方法:

  Lock

  Unlock

如果函数体中需要一直使用共享数据到函数结束的话,则可以用 defer 配合 Unlock 来保证函数结束后一定会执行解锁操作来保证其它的 goroutine 正常执行。

var _IncNum = 0
var _Mux sync.Mutex

func Inc() {
	for {
		_Mux.Lock()
		if _IncNum < 1000 {
			_IncNum++
		} else {
			_Mux.Unlock()
			return
		}
		_Mux.Unlock()
		time.Sleep(5 * time.Millisecond)
	}
}

func PrintIncNum(complateChannel chan int) {
	for {
		if !CheckIncNumCondition() {
			break
		}
		time.Sleep(50 * time.Millisecond)
	}
	complateChannel <- 1
}

func CheckIncNumCondition() bool {
	defer _Mux.Unlock()
	_Mux.Lock()
	return _IncNum < 1000
}

func main() {
	complateCh := make(chan int)
	go Inc()
	go Inc()
	go Inc()
	go Inc()
	go Inc()
	go PrintIncNum(complateCh)
	if result := <-complateCh; result > 0 {
		fmt.Println("_IncNum : ", _IncNum)
	}
}

 

posted @ 2017-08-17 01:01  问号是我  阅读(154)  评论(0编辑  收藏  举报