GO千练——并发

Go 程

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

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

goroutine 语法格式:

go 函数名(参数列表)

实例:

package main

import (
	"fmt"
	"time"
)

func say(msg string) {
	for i := 0; i < 5; i++ {
		time.Sleep(1000)
		fmt.Println(msg)
	}
}

func main() {
	go say("hello")
	say("go")
}

信道(channel)

概念

信道(channel)是用来传递数据的一个数据结构。

信道在使用前必须创建,然后我们可以通过它用信道操作符 <- 来发送或者接收值。

// 创建
ch := make(chan int)

// “箭头”就是数据流的方向。
ch <- v    // 将 v 发送至信道 ch。
v := <-ch  // 从 ch 接收值并赋予 v。

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

实例:

package main

import "fmt"

func sum(s []int, ch chan int) {
	var sum int = 0
	for _, v := range s {
		sum += v
	}
	ch <- sum
}

func main() {
	arr := []int{1, 4, 7, 3, 6, 9}
	ch := make(chan int)
	go sum(arr[:len(arr)/2], ch)
	go sum(arr[len(arr)/2:], ch)

	x, y := <-ch, <-ch

	fmt.Printf("和为:%v", (x + y))
}
带缓冲的信道

信道可以是带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

// 第二个参数为缓存容量
ch := make(chan int, 100)

实例:

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	ch <- 2
	
	// fatal error: all goroutines are asleep - deadlock!
	// ch <- 3
	
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	
	// fatal error: all goroutines are asleep - deadlock!
	// fmt.Println(<-ch)
}
range 和 close

发送者可通过 close 关闭一个信道来表示没有需要发送的值了。
接收者可以通过为接收表达式分配第二个参数来判断信道是否被关闭:若没有值可以接收且信道已被关闭,那么再执行完。

// 信道关闭之后 ok 会被设置为 false。
v, ok := <-ch

// for range 不断从信道接收值,直到它被关闭。
for i := range c

注意

  • 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
  • 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭, 例如终止一个 range 循环。

实例:

package main

import (
	"fmt"
)

func fibonacci(n int, ch chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		x, y = y, x+y
		ch <- x
	}
	// 关闭信道
	close(ch)
}

func main() {
	ch := make(chan int, 10)
	go fibonacci(cap(ch), ch)

	for i := range ch {
		fmt.Println(i)
	}
}
select 语句

select 语句使一个 Go 程可以等待多个通信操作。
select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。

package main

import "fmt"

func fibonacci(ch chan int, quit chan int) {
	x, y := 1, 1
	for {
		// 多个通信操作
		select {
		case ch <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	ch := make(chan int)
	quit := make(chan int)

	// 异步打印
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-ch)
		}
		quit <- 0
	}()

	fibonacci(ch, quit)
}
默认选择

当 select 中的其它分支都没有准备好时,default 分支就会执行。
为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:

select {
case i := <-ch:
    // 使用 i
default:
    // 从 ch 中接收会阻塞时执行
}

实例代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
            // 别的信道分枝没准备好则执行默认
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

互斥锁 sync.Mutex

如果我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

Lock
Unlock

实例代码:线程安全累加器

package main

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

// 定义线程安全计数器
type SafeCounter struct {
	mux sync.Mutex
	v   map[string]int
}

// 定义递增方法
func (sc *SafeCounter) Incr(key string) {
	sc.mux.Lock()
	sc.v[key]++
	sc.mux.Unlock()
}

// 定义取值方法
func (sc *SafeCounter) Get(key string) int {
	sc.mux.Lock()
	// 保证返回值之后一定解锁
	defer sc.mux.Unlock()
	return sc.v[key]
}

func main() {
	sc := SafeCounter{v: make(map[string]int)}

	// 累加1000
	for i := 0; i < 1000; i++ {
		// 异步递加
		go sc.Incr("somekey")
	}

	// 睡眠方便递加程序执行完毕
	time.Sleep(time.Second)
	fmt.Println(sc.Get("somekey"))
}

请关注公众号【Java千练】,更多干货文章等你来看!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值