golang并发编程

https://go101.org/article/channel-use-cases.html

https://go101.org/article/101.html 这书不错

像Futures/Promises一样使用channels

将receive-only channels作为返回值

package main

import (

"time"

"math/rand"

"fmt"

)

func longTimeRequest() <-chan int32 {

r := make(chan int32)

go func() {

// Simulate a workload.

time.Sleep(time.Second * 3)

r <- rand.Int31n(100)

}()

return r

}

func sumSquares(a, b int32) int32 {

return a*a + b*b

}

func main() {

rand.Seed(time.Now().UnixNano())

a, b := longTimeRequest(), longTimeRequest()

fmt.Println(sumSquares(<-a, <-b))

}

将send-only channels作为参数

package main

import (

"time"

"math/rand"

"fmt"

)

func longTimeRequest(r chan<- int32)  {

// Simulate a workload.

time.Sleep(time.Second * 3)

r <- rand.Int31n(100)

}

func sumSquares(a, b int32) int32 {

return a*a + b*b

}

func main() {

rand.Seed(time.Now().UnixNano())

ra, rb := make(chan int32), make(chan int32)

go longTimeRequest(ra)

go longTimeRequest(rb)

fmt.Println(sumSquares(<-ra, <-rb))

}

上面两个例子,用了两个channels,可以用一个就好

// The channel can be buffered or not.

results := make(chan int32, 2)

go longTimeRequest(results)

go longTimeRequest(results)

fmt.Println(sumSquares(<-results, <-results))

第一响应获胜

这个是对上面只用一个channel例子的增强

有时为了避免高延迟,会同时向多个数据源获取同一份数据。由于各种原因,各个数据源的响应时间会差别很大,即使同一个数据源也不会固定。为了减少响应时间,我们在独立的goroutine中向每个数据源发起请求。最先响应获得使用,其它被抛弃。

注意:假如有n个数据源,channel的容量至少是n-1.主要避免被抛弃的会被永久阻塞。

package main

import (

"fmt"

"time"

"math/rand"

)

func source(c chan<- int32) {

ra, rb := rand.Int31(), rand.Intn(3) + 1

// Sleep 1s/2s/3s.

time.Sleep(time.Duration(rb) * time.Second)

c <- ra

}

func main() {

rand.Seed(time.Now().UnixNano())

startTime := time.Now()

// c must be a buffered channel.

c := make(chan int32, 5)

for i := 0; i < cap(c); i++ {

go source(c)

}

// Only the first response will be used.

rnd := <- c

fmt.Println(time.Since(startTime))

fmt.Println(rnd)

}

更多的请求应答变量

使用buffer channel作为参数或返回值,可以避免响应方要等待请求方取出数据。

有时响应方不保证返回有效数据,这时可能要返回错误。我们使用struct{v T; err error} or a blank interface type作为channel数据类型。

有时还要个超时机制。

用channel来实现消息通知

一对一通知,通过给channel发送一个值

package main

import (

"crypto/rand"

"fmt"

"os"

"sort"

)

func main() {

values := make([]byte, 32 * 1024 * 1024)

if _, err := rand.Read(values); err != nil {

fmt.Println(err)

os.Exit(1)

}

done := make(chan struct{}) // can be buffered or not

// The sorting goroutine

go func() {

sort.Slice(values, func(i, j int) bool {

return values[i] < values[j]

})

// Notify sorting is done.

done <- struct{}{}

}()

// do some other things ...

<- done // waiting here for notification

fmt.Println(values[0], values[len(values)-1])

}

一对一通知,通过接收channel的值

package main

import (

"fmt"

"time"

)

func main() {

done := make(chan struct{})

// The capacity of the signal channel can

// also be one. If this is true, then a

// value must be sent to the channel before

// creating the following goroutine.

go func() {

fmt.Print("Hello")

// Simulate a workload.

time.Sleep(time.Second * 2)

// Receive a value from the done

// channel, to unblock the second

// send in main goroutine.

<- done

}()

// Blocked here, wait for a notification.

done <- struct{}{}

fmt.Println(" world!")

}

这个比前那个显得较为奇葩。它是基于unbuffered channel总是满的状态,发送会被阻塞直到有人取数据。

一对多和多对一通知

package main

import "log"

import "time"

type T = struct{}

func worker(id int, ready <-chan T, done chan<- T) {

<-ready // block here and wait a notification

log.Print("Worker#", id, " starts.")

// Simulate a workload.

time.Sleep(time.Second * time.Duration(id+1))

log.Print("Worker#", id, " job done.")

// Notify the main goroutine (N-to-1),

done <- T{}

}

func main() {

log.SetFlags(0)

ready, done := make(chan T), make(chan T)

go worker(0, ready, done)

go worker(1, ready, done)

go worker(2, ready, done)

// Simulate an initialization phase.

time.Sleep(time.Second * 3 / 2)

// 1-to-N notifications.

ready <- T{}; ready <- T{}; ready <- T{}

// Being N-to-1 notified.

<-done; <-done; <-done

}

实际上很少这样用法。更常用的是sync.WaitGroup和关闭channels。

通过close channel通知多个goroutine

计时器

package main

import (

"fmt"

"time"

)

func AfterDuration(d time.Duration) <- chan struct{} {

c := make(chan struct{}, 1)

go func() {

time.Sleep(d)

c <- struct{}{}

}()

return c

}

func main() {

fmt.Println("Hi!")

<- AfterDuration(time.Second)

fmt.Println("Hello!")

<- AfterDuration(time.Second)

fmt.Println("Bye!")

}

用channel作为mutex locks

用一个容量是1的buffer channel作为mutex lock的两种方法

通过send来锁,receive解锁

通过receive来锁,send解锁

package main

import "fmt"

func main() {

// The capacity must be one.

mutex := make(chan struct{}, 1)

counter := 0

increase := func() {

mutex <- struct{}{} // lock

counter++

<-mutex // unlock

}

increase1000 := func(done chan<- struct{}) {

for i := 0; i < 1000; i++ {

increase()

}

done <- struct{}{}

}

done := make(chan struct{})

go increase1000(done)

go increase1000(done)

<-done; <-done

fmt.Println(counter) // 2000

}

用Channels作为计数信号量Counting Semaphores

用buffer channel来实现,buffer的容量就是资源数据

同样可以有两种实现,send receive互换

package main

import (

"log"

"time"

"math/rand"

)

type Seat int

type Bar chan Seat

func (bar Bar) ServeCustomer(c int) {

log.Print("customer#", c, " enters the bar")

seat := <- bar // need a seat to drink

log.Print("++ customer#", c, " drinks at seat#", seat)

time.Sleep(time.Second * time.Duration(2 + rand.Intn(6)))

log.Print("-- customer#", c, " frees seat#", seat)

bar <- seat // free seat and leave the bar

}

func main() {

rand.Seed(time.Now().UnixNano())

// the bar has 10 seats.

bar24x7 := make(Bar, 10)

// Place seats in an bar.

for seatId := 0; seatId < cap(bar24x7); seatId++ {

// None of the sends will block.

bar24x7 <- Seat(seatId)

}

for customerId := 0; ; customerId++ {

time.Sleep(time.Second)

go bar24x7.ServeCustomer(customerId)

}

// sleeping != blocking

for {time.Sleep(time.Second)}

}

上面会在没空位时就创建了goroutine, 下面是在有空位置时才创建ServeCustomer goruotine

func (bar Bar) ServeCustomerAtSeat(c int, seat Seat) {

log.Print("customer#", c, " drinks at seat#", seat)

time.Sleep(time.Second * time.Duration(2 + rand.Intn(6)))

log.Print("<- customer#", c, " frees seat#", seat)

bar <- seat // free seat and leave the bar

}

func main() {

rand.Seed(time.Now().UnixNano())

bar24x7 := make(Bar, 10)

for seatId := 0; seatId < cap(bar24x7); seatId++ {

bar24x7 <- Seat(seatId)

}

for customerId := 0; ; customerId++ {

time.Sleep(time.Second)

// Need a seat to serve next customer.

seat := <- bar24x7

go bar24x7.ServeCustomerAtSeat(customerId, seat)

}

for {time.Sleep(time.Second)}

}

前面是通过receive实现,下面通过sending实现

package main

import (

"log"

"time"

"math/rand"

)

type Customer struct{id int}

type Bar chan Customer

func (bar Bar) ServeCustomer(c Customer) {

log.Print("++ customer#", c.id, " starts drinking")

time.Sleep(time.Second * time.Duration(3 + rand.Intn(16)))

log.Print("-- customer#", c.id, " leaves the bar")

<- bar // leaves the bar and save a space

}

func main() {

rand.Seed(time.Now().UnixNano())

// The bar can serve most 10 customers

// at the same time.

bar24x7 := make(Bar, 10)

for customerId := 0; ; customerId++ {

time.Sleep(time.Second * 2)

customer := Customer{customerId}

// Wait to enter the bar.

bar24x7 <- customer

go bar24x7.ServeCustomer(customer)

}

for {time.Sleep(time.Second)}

}

Dialogue (Ping-Pong)

package main

import "fmt"

import "time"

import "os"

type Ball uint64

func Play(playerName string, table chan Ball) {

var lastValue Ball = 1

for {

ball := <- table // get the ball

fmt.Println(playerName, ball)

ball += lastValue

if ball < lastValue { // overflow

os.Exit(0)

}

lastValue = ball

table <- ball // bat back the ball

time.Sleep(time.Second)

}

}

func main() {

table := make(chan Ball)

go func() {

table <- 1 // throw ball on table

}()

go Play("A:", table)

Play("B:", table)

}

channel中包含channel

package main

import "fmt"

var counter = func (n int) chan<- chan<- int {

requests := make(chan chan<- int)

go func() {

for request := range requests {

if request == nil {

n++ // increase

} else {

request <- n // take out

}

}

}()

// Implicitly converted to chan<- (chan<- int)

return requests

}(0)

func main() {

increase1000 := func(done chan<- struct{}) {

for i := 0; i < 1000; i++ {

counter <- nil

}

done <- struct{}{}

}

done := make(chan struct{})

go increase1000(done)

go increase1000(done)

<-done; <-done

request := make(chan int, 1)

counter <- request

fmt.Println(<-request) // 2000

}

永久阻塞

select{}

用select default实现try send,try receive,这样就不会阻塞

package main

import "fmt"

func main() {

type Book struct{id int}

bookshelf := make(chan Book, 3)

for i := 0; i < cap(bookshelf) * 2; i++ {

select {

case bookshelf <- Book{id: i}:

fmt.Println("succeeded to put book", i)

default:

fmt.Println("failed to put book")

}

}

for i := 0; i < cap(bookshelf) * 2; i++ {

select {

case book := <-bookshelf:

fmt.Println("succeeded to get book", book.id)

default:

fmt.Println("failed to get book")

}

}

}

channel IsClose

package main

import (

"fmt"

)

func IsClosed(c chan int) bool { //only work for non-buffered channel

select {

case <-c:

return true

default:

}

return false

}

func main() {

cc := make(chan int,1) //here is buffered channel

cc <-1

fmt.Println(IsClosed(cc))

fmt.Println(IsClosed(cc))

close(cc)

fmt.Println(IsClosed(cc))

}

//output:

//true

//false

//true

峰值/突发限制Peak/burst limiting

也就是counting semaphores

...

// Can serve most 10 customers at the same time

bar24x7 := make(Bar, 10)

for customerId := 0; ; customerId++ {

time.Sleep(time.Second)

customer := Consumer{customerId}

select {

case bar24x7 <- customer: // try to enter the bar

go bar24x7.ServeConsumer(customer)

default:

log.Print("customer#", customerId, " goes elsewhere")

}

}

...

first-response-wins

容量1的channel,通过select default将第2之后的值抛弃

package main

import (

"fmt"

"math/rand"

"time"

)

func source(c chan<- int32) {

ra, rb := rand.Int31(), rand.Intn(3)+1

// Sleep 1s, 2s or 3s.

time.Sleep(time.Duration(rb) * time.Second)

select {

case c <- ra:

default:

}

}

func main() {

rand.Seed(time.Now().UnixNano())

// The capacity should be at least 1.

c := make(chan int32, 1)

for i := 0; i < 5; i++ {

go source(c)

}

rnd := <-c // only the first response is used

fmt.Println(rnd)

}

first win另一种实现

package main

import (

"fmt"

"math/rand"

"time"

)

func source() <-chan int32 {

// c must be a buffered channel.

c := make(chan int32, 1)

go func() {

ra, rb := rand.Int31(), rand.Intn(3)+1

time.Sleep(time.Duration(rb) * time.Second)

c <- ra

}()

return c

}

func main() {

rand.Seed(time.Now().UnixNano())

var rnd int32

// Blocking here until one source responses.

select{

case rnd = <-source():

case rnd = <-source():

case rnd = <-source():

}

fmt.Println(rnd)

}

注意:如果上面source中的channel是一个unbuffered channel,会导致select中会有两个case永久阻塞。这就导致内存阻塞。

超时timeout

func requestWithTimeout(timeout time.Duration) (int, error) {

c := make(chan int)

// May need a long time to get the response.

go doRequest(c)

select {

case data := <-c:

return data, nil

case <-time.After(timeout):

return 0, errors.New("timeout")

}

}

间隔执行品Ticker

package main

import "fmt"

import "time"

func Tick(d time.Duration) <-chan struct{} {

// The capacity of c is best set as one.

c := make(chan struct{}, 1)

go func() {

for {

time.Sleep(d)

select {

case c <- struct{}{}:

default:

}

}

}()

return c

}

func main() {

t := time.Now()

for range Tick(time.Second) {

fmt.Println(time.Since(t))

}

}

————————————————

版权声明:本文为CSDN博主「huafable007」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/huafable007/article/details/106281574

我先在csdn发表的,csdn代码格式好看些,下回考虑用markdown格式看看。

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