GO channel类型

channel是go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用分布式系统的方法来解决,比如使用socket或者http等通信协议。

channel是与类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。如果对Unix管道有所理解的话,就不难理解channel,可以将其认为是一种类型安全的管道。

A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type. The value of an uninitialized channel is nil.


channel变量的声明形式:

var chanName (chan | chan <- | <- chan) ElementType

The optional <- operator specifies the channel directionsend or receive. If no direction is given, the channel is bidirectional(双向的).

chan T          // can be used to send and receive values of type T
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints
 

The <- operator associates with the leftmost chan possible:

chan<- chan int    // same as chan<- (chan int)
chan<- <-chan int  // same as chan<- (<-chan int)
<-chan <-chan int  // same as <-chan (<-chan int)
chan (<-chan int)

A new, initialized channel value can be made using the built-in function make,

which takes the channel type and an optional capacity as arguments:

make(chan int, 100)
The capacity, in number of elements, sets the size of the buffer in the channel.
If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. 
Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives).
nil channel is never ready for communication.

A channel may be closed with the built-in function close. The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.
For a channel c, the built-in function close(c) records that no more values will be sent on the channel. 
It is an error if c is a receive-only channel. 
Sending to or closing a closed channel causes a run-time panic. 
Closing the nil channel also causes a run-time panic. 
After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel's type without blocking. 
The multi-valued receive operation returns a received value along with an indication(布尔类型。通道开着,返回true;通道关闭,返回false) of whether the channel is closed.
 

A single channel may be used in send statementsreceive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization. Channels act as first-in-first-out queues. For example, if one goroutine sends values on a channel and a second goroutine receives them, the values are received in the order sent.

下面列举了channel的一些使用场景。

场景1:生产者/消费者

生产者产生一些数据将其放入 channel,然后消费者按照顺序,一个一个的从 channel 中取出这些数据进行处理。这是最常见的 channel 的使用方式。当 channel 的缓冲用尽时,生产者必须等待(阻塞);若是 channel 中没有数据,消费者就必须等待了。

//main.go

package main
 
import "fmt"
import "time"
 
/*
producer函数每隔500毫秒向channel里发送一个整数
*/
func producer(ch chan<- int) { //这里的ch是send only channel
    var i = 1
    for {
        ch <- i
        time.Sleep(time.Millisecond * 500)
        i++
    }
}
 
/*
consumer函数每隔1秒从channel里读取一个整数
*/
func consumer(ch <-chan int) { //这里的channel是receive only channel
    for val := range ch {
        fmt.Println("consumer received", val)
        time.Sleep(time.Second)
    }
}
 
func main() {
    var ch = make(chan int, 6)
    var sign chan bool //声明一个信号channel,值为nil
    go producer(ch)    //创建一个goroutinue,去执行producer函数
    go consumer(ch)    //创建一个goroutinue,去执行consumer函数
    <-sign             //sign == nil,所以该操作会导致主goroutinue阻塞
}
 

运行结果:

C:/go/bin/go.exe run test9.go [E:/project/go/proj/src/test]

consumer received 1

consumer received 2

consumer received 3

consumer received 4

consumer received 5

consumer received 6

consumer received 7

consumer received 8

consumer received 9

...

场景2:自增长 ID 生成器

自增长ID生成器类似于生产者,只是该生产者并不是一下子产生好多整数,而是当消费者需要的时候才会顺序的产生一个数字。

下面的代码我将自增长ID生成器封装成一个包。

//autoincre
package autoincre
 
type AutoIncre struct {
    start, step int
    queue       chan int
}
 
func New(start, step int) (ai *AutoIncre) {
    ai = &AutoIncre{
        start: start,
        step:  step,
        queue: make(chan int),
    }
 
    go ai.process()
 
    return
}
 
func (ai *AutoIncre) process() {
    var i int = 1
    ok := true
    for ok {
        select {
        case ai.queue <- i:
            i++
        }
    }
}
 
func (ai *AutoIncre) Next() int {
    return <-ai.queue
}
 

//main.go

package main
 
import "autoincre"
import "fmt"
 
func main() {
    var ai = autoincre.New(1, 1)
 
    for i := 1; i <= 10; i++ {
        fmt.Println(ai.Next())
    }
}
运行结果:

C:/go/bin/go.exe run myapp.go [E:/project/go/proj/src/myapp]

1

2

3

4

5

6

7

8

9

10

成功: 进程退出代码 0.


场景3:信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。

下面的案例使用channel的特性来模拟信号量。

package main
 
import "fmt"
import "time"
 
var maxOutstanding int = 1000 //最大处理数
var sem = make(chan int, maxOutstanding)
 
func handle(x int) {
    sem <- 1 //等待放行
    fmt.Println(x)
    time.Sleep(time.Millisecond * 20) //模拟处理时间
    <-sem                             //完成,放行另一个过程
}
 
/*
serve函数负责接收外界的请求,同时将请求分发给handle函数进行处理
*/
func serve(arr []int) {
    for i := 0; i < len(arr); i++ {
        go handle(arr[i]) //每个请求由单独的goroutine负责处理
    }
}
 
func main() {
    var arr []int
    var sign chan bool
 
    for i := 1; i <= 10000; i++ {
        arr = append(arr, i)
    }
    serve(arr)
    <-sign
}
 

场景4:随机序列生成器

package main
 
import "fmt"
 
func producer(ch chan int, length int) {
    defer close(ch) //函数结束时关闭channel
    for i := 1; i <= length; i++ {
        select { //随机向channel添加0|1
        case ch <- 0:
        case ch <- 1:
        }
    }
 
}
 
func main() {
    var ch chan int = make(chan int, 10)
    var val int
    ok := true
 
    go producer(ch, cap(ch))
    for ok {
        select {
        case val, ok = <-ch:
            if !ok { //ok==false时,表示channel已关闭
                break //退出select语句
            }
            fmt.Print(val)
        }
    }
    fmt.Println()
}
 运行结果: 

C:/go/bin/go.exe run test2.go [E:/project/go/proj/src/test]

0111001110

成功: 进程退出代码 0.

场景5:超时定时器  (一次性)

我们知道,向一个已经满了的channel写数据,会导致该goroutinue阻塞或者向一个空的channel读数据,会导致该channel阻塞,而且是会一直阻塞下去。

可是现在的程序都是异步的,主程序不会因为一个I/O操作而被长期阻塞。这个时候,需要我们自己实现一个超时机制。

//该案例使用一个单独的goroutinue来实现超时

//这种方式的不合理之处有用来判断超时的goroutinue提前运行了,而不是在执行IO操作的时候进行判断,

//同时,这种方式,超时机制只被使用了一次,之后的IO操作就不进行了。

//main.go

package main
 
import "time"
import "fmt"
 
func f1(ch chan int, max int) {
    defer close(ch)
    for i := 1; i <= max; i++ {
        if i <= (max / 2) {
            time.Sleep(time.Millisecond * 100)
        } else {
            time.Sleep(time.Second)
        }
        ch <- i
    }
}
 
func main() {
    var ch = make(chan int)
    var timeout = make(chan int)
    var val int
    ok := true
 
    go f1(ch, 10)
 
    go func() {
        time.Sleep(time.Millisecond * 500)
        timeout <- 1
    }()
 
    for ok {
        select {
        case val, ok = <-ch:
            if !ok {
                fmt.Println("The channel has been closed.")
                break
            }
            fmt.Println("received :", val)
        case <-timeout:
            fmt.Println("timeout occurs.")
        }
    }
    fmt.Println("Done.")
}
 运行结果: 

C:/go/bin/go.exe run test3.go [E:/project/go/proj/src/test]

received : 1

received : 2

received : 3

received : 4

received : 5

timeout occurs.

received : 6

received : 7

received : 8

received : 9

received : 10

The channel has been closed.

Done.

成功: 进程退出代码 0.

场景5:超时定时器  (改进版)

//该版本优化了超时机制,在每次执行select的时候,触发超时函数的执行,进而对超时的判断更加精准,

//并且每一次的IO操作都增加了超时判断。

//main.go

package main
 
import "time"
import "fmt"
 
func f1(ch chan int, max int) {
    defer close(ch)
    for i := 1; i <= max; i++ {
        if i <= (max / 2) {
            time.Sleep(time.Millisecond * 100)
        } else {
            time.Sleep(time.Second)
        }
        ch <- i
    }
}
 
func main() {
    var ch = make(chan int)
    var val int
    ok := true
 
    go f1(ch, 10)
 
    for ok {
        select {
        case val, ok = <-ch:
            if !ok {
                fmt.Println("The channel has been closed.")
                break
            }
            fmt.Println("received :", val)
        case <-func() chan int {
            var timeout = make(chan int)
            go func() {
                time.Sleep(time.Millisecond * 500)
                timeout <- 1
            }()
            return timeout
        }():
            fmt.Println("timeout occurs.")
        }
    }
    fmt.Println("Done.")
}
 运行结果: 

C:/go/bin/go.exe run test3.go [E:/project/go/proj/src/test]

received : 1

received : 2

received : 3

received : 4

received : 5

timeout occurs.

timeout occurs.

received : 6

timeout occurs.

timeout occurs.

received : 7

timeout occurs.

timeout occurs.

received : 8

timeout occurs.

timeout occurs.

received : 9

timeout occurs.

timeout occurs.

received : 10

The channel has been closed.

Done.

成功: 进程退出代码 0.

场景6:超时定时器  (优化版)

//该版本优化了超时机制,之前的超时机制是自定义实现的,这个版本里我们将使用库函数time.After来实现超时判断。

//main.go

package main
 
import "time"
import "fmt"
 
func f1(ch chan int, max int) {
    defer close(ch)
    for i := 1; i <= max; i++ {
        if i <= (max / 2) {
            time.Sleep(time.Millisecond * 100)
        } else {
            time.Sleep(time.Second)
        }
        ch <- i
    }
}
func main() {
    var ch = make(chan int)
    var val int
    ok := true
    go f1(ch, 10)
    for ok {
        select {
        case val, ok = <-ch:
            if !ok {
                fmt.Println("The channel has been closed.")
                break
            }
            fmt.Println("received :", val)
        case <-time.After(time.Millisecond * 500):
            fmt.Println("timeout occurs.")
        }
    }
    fmt.Println("Done.")
}
 运行结果: 

C:/go/bin/go.exe run test5.go [E:/project/go/proj/src/test]

received : 1

received : 2

received : 3

received : 4

received : 5

timeout occurs.

timeout occurs.

received : 6

timeout occurs.

timeout occurs.

received : 7

timeout occurs.

timeout occurs.

received : 8

timeout occurs.

timeout occurs.

received : 9

timeout occurs.

timeout occurs.

received : 10

The channel has been closed.

Done.

成功: 进程退出代码 0.

可以看出,通过使用time.After代码简洁了很多。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

历史五千年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值