• 【转】 Anatomy of Channels in Go


    原文:https://medium.com/rungo/anatomy-of-channels-in-go-concurrency-in-go-1ec336086adb

    --------------------------------

    What are the channels?

    channel is a communication object using which goroutines can communicate with each other. Technically, a channel is a data transfer pipe where data can be passed into or read from. Hence one goroutine can send data into a channel, while other goroutines can read that data from the same channel.

    Declaring a channel

    Go provides chan keyword to create a channel. A channel can transport data of only one data type. No other data types are allowed to be transported from that channel.

    https://play.golang.org/p/iWOFLfcgfF-

    https://play.golang.org/p/N4dU7Ql9bK7

    type of `c` is chan int
    value of `c` is 0xc0420160c0

    Data read and write

    Go provide very easy to remember left arrow syntax <- to read and write data from a channel.

    c <- data
    <- c
    var data int
    data = <- c
    data := <- c

    Channels in practice

    Enough talking among us, let’s talk to a goroutine

    https://play.golang.org/p/OeYLKEz7qKi
    • In the main function, program prints main started to the console as it is the first statement.
    • Then we made the channel c of type string using make function.
    • We passed channel c to the greet function but executed it as a goroutine using go keyword.
    • At this point, the process has 2 goroutines while active goroutine is main goroutine (check the previous lesson to know what it is). Then control goes to the next line.
    • We pushed a string value John to channel c. At this point, goroutine is blocked until some goroutine reads it. Go scheduler schedule greet goroutine and it’s execution starts as per mentioned in the first point.
    • Then main goroutine becomes active and execute the final statement, printing main stopped.

    Deadlock

    As discussed, when we write or read data from a channel, that goroutine is blocked and control is passed to available goroutines. What if there are no other goroutines available, imagine all of them are sleeping. That’s where deadlock error occurs crashing the whole program.

    https://play.golang.org/p/2KTEoljdci_f
    main() started
    fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
    main.main()
    program.Go:10 +0xfd
    exit status 2

    ☛ Closing a channel

    A channel can be closed so that no more data can be sent through it. Receiver goroutine can find out the state of the channel using val, ok := <- channel syntax where ok is true if the channel is open or read operations can be performed and false if the channel is closed and no more read operations can be performed. A channel can be closed using close built-in function with syntax close(channel). Let’s see a simple example.

    https://play.golang.org/p/LMmAq4sgm02

    ☛ For loop

    An infinite syntax for loop for{} can be used to read multiple values sent through a channel.

    https://play.golang.org/p/X58FTgSHhXi
    main() started
    0 true
    1 true
    4 true
    9 true
    16 true
    25 true
    36 true
    49 true
    64 true
    81 true
    0 false <-- loop broke!
    main() stopped
    https://play.golang.org/p/ICCYbWO7ZvD
    main() started
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81
    main() stopped

    ☛ Buffer size or channel capacity

    As we saw, every send operation on channel blocks the current goroutine. But so far we used make function without the second parameter. This second parameter is the capacity of a channel or the buffer size. By default, a channel buffer size is 0 also called as unbuffered channel. Whatever written to the channel is immediately available to read.

    c := make(chan Type, n)
    https://play.golang.org/p/k0usdYZfp3D
    https://play.golang.org/p/KGyiskRj1Wi

    length and capacity of a channel

    Similar to a slice, a buffered channel has length and capacity. Length of a channel is the number of values queued (unread) in channel buffer while the capacity of a channel is the buffer size. To calculate length, we use len function while to find out capacity, we use cap function, just like a slice.

    https://play.golang.org/p/-gGpm08-wzz
    https://play.golang.org/p/sdHPDx64aor
    https://play.golang.org/p/vULFyWnpUoj

    Working with multiple goroutines

    Let’s write 2 goroutines, one for calculating the square of integers and other for the cube of integers.

    https://play.golang.org/p/6wdhWYpRfrX
    • In the main goroutine, we create 2 channels squareChan and cubeChan of type int using make function.
    • Then we run square and cube goroutine.
    • Since control is still inside the main goroutine, testNumb variable gets the value of 3.
    • Then we push data to squareChan and cubeChan. The main goroutine will be blocked until these channels read it from. Once they read it, the main goroutine will continue execution.
    • When in the main goroutine, we try to read data from given channels, control will be blocked until these channels write some data from their goroutines. Here, we have used shorthand syntax := to receive data from multiple channels.
    • Once these goroutines write some data to the channel, the main goroutine will be blocked.
    • When the channel write operation is done, the main goroutine starts executing. Then we calculate the sum and print it on the console.
    [main] main() started
    [main] sent testNum to squareChan
    [square] reading
    [main] resuming
    [main] sent testNum to cubeChan
    [cube] reading
    [main] resuming
    [main] reading from channels
    [main] sum of square and cube of 3 is 36
    [main] main() stopped

    ☛ Unidirectional channels

    So far, we have seen channels which can transmit data from both sides or in simple words, channels on which we can do read and write operations. But we can also create channels which are unidirectional in nature. For example, receive-only channels which allow only read operation on them and send-only channels which allow only to write operation on them.

    roc := make(<-chan int)
    soc := make(chan<- int)
    https://play.golang.org/p/JZO51IoaMg8
    https://play.golang.org/p/k3B3gCelrGv

    ☛ Anonymous goroutine

    In goroutines chapter, we learned about anonymous goroutines. We can also implement channels with them. Let’s modify the previous simple example to implement channel in an anonymous goroutine.

    https://play.golang.org/p/c5erdHX1gwR
    https://play.golang.org/p/cM5nFgRha7c

    ☛ channel as the data type of channel

    Yes, channels are first-class values and can be used anywhere like other values: as struct elements, function arguments, returning values and even like a type for another channel. Here, we are interested in using a channel as the data type of another channel.

    https://play.golang.org/p/xVQvvb8O4De

    ☛ Select

    select is just like switch without any input argument but it only used for channel operations. The select statement is used to perform an operation on only one channel out of many, conditionally selected by case block.

    https://play.golang.org/p/ar5dZUQ2ArH
    https://play.golang.org/p/giSkkqt8XHb
    main() started 0s
    service2() started 481µs
    Response from service 2 Hello from service 2 981.1µs
    main() stopped 981.1µs
    main() started 0s
    service1() started 484.8µs
    Response from service 1 Hello from service 1 984µs
    main() stopped 984µs
    https://play.golang.org/p/RLRGEmFQP3f
    main() started 0s
    Response from chan2 Value 1 0s
    main() stopped 1.0012ms
    main() started 0s
    Response from chan1 Value 1 0s
    main() stopped 1.0012ms

    default case

    Like switch statement, select statement also has default case. A default case is non-blocking. But that’s not all, default case makes select statement always non-blocking. That means, send and receive operation on any channel (buffered or unbuffered) is always non-blocking.

    https://play.golang.org/p/rFMpc80EuT3
    https://play.golang.org/p/eD0NHxHm9hN
    main() started 0s
    service1() started 0s
    service2() started 0s
    Response from service 1 Hello from service 1 3.0001805s
    main() stopped 3.0001805s
    main() started 0s
    service1() started 0s
    service2() started 0s
    Response from service 2 Hello from service 2 3.0000957s
    main() stopped 3.0000957s

    Deadlock

    default case is useful when no channels are available to send or receive data. To avoid deadlock, we can use default case. This is possible because all channel operations due to default case are non-blocking, Go does not schedule any other goroutines to send data to channels if data is not immediately available.

    https://play.golang.org/p/S3Wxuqb8lMF

    ☛ nil channel

    As we know, the default value of a channel is nil. Hence we can not perform send or receive operations on a nil channel. But in a case, when a nil channel is used in select statement, it will throw one of the below or both errors.

    https://play.golang.org/p/uhraFubcF4S
    https://play.golang.org/p/upLsz52_CrE

    ☛ Adding timeout

    Above program is not very useful since only default case is getting executed. But sometimes, what we want is that any available services should respond in a desirable time, if it doesn’t, then default case should get executed. This can be done using a case with a channel operation that unblocks after defined time. This channel operation is provided by time package’s After function. Let’s see an example.

    https://play.golang.org/p/mda2t2IQK__X
    main() started 0s
    No response received 2.0010958s
    main() stopped 2.0010958s

    ☛ Empty select

    Like for{} empty loop, an empty select{} syntax is also valid but there is a gotcha. As we know, select statement is blocked until one of the cases unblocks, and since there are no case statements available to unblock it, the main goroutine will block forever resulting in a deadlock.

    https://play.golang.org/p/-pBd-BLMFOu
    main() started
    Hello from service!
    fatal error: all goroutines are asleep - deadlock!goroutine 1 [select (no cases)]:
    main.main()
    program.Go:16 +0xba
    exit status 2

    ☛ WaitGroup

    Let’s imagine a condition where you need to know if all goroutines finished their job. This is somewhat opposite to select where you needed only one condition to be true, but here you need all conditions to be true in order to unblock the main goroutine. Here the condition is successful channel operation.

    https://play.golang.org/p/8qrAD9ceOfJ
    main() started
    Service called on instance 2
    Service called on instance 3
    Service called on instance 1
    main() stopped

    ☛ Worker pool

    As from the name, a worker pool is a collection of goroutines working concurrently to perform a job. In WaitGroup, we saw a collection of goroutines working concurrently but they did not have a specific job. Once you throw channels in them, they have some job to do and becomes a worker pool.

    https://play.golang.org/p/IYiMV1I4lCj
    • In the main function, we created tasks and result channel with buffer capacity 10. Hence any send operation will be non-blocking until the buffer is full. Hence setting large buffer value is not a bad idea.
    • Then we spawn multiple instances of sqrWorker as goroutines with above two channels and id parameter to get information on which worker is executing a task.
    • Then we passed 5 tasks to the tasks channel which was non-blocking.
    • Since we are done with tasks channel, we closed it. This is not necessary but it will save a lot of time in the future if some bugs get in.
    • Then using for loop, with 5 iterations, we are pulling data from results channel. Since read operation on an empty buffer is blocking, a goroutine will be scheduled from the worker pool. Until that goroutine returns some result, the main goroutine will be blocked.
    • Since we are simulating blocking operation in worker goroutine, that will call the scheduler to schedule another available goroutine until it becomes available. When worker goroutine becomes available, it writes to the results channel. As writing to buffered channel is non-blocking until the buffer is full, writing to results channel here is non-blocking. Also while current worker goroutine was unavailable, multiple other worker goroutines were executed consuming values in tasks buffer. After all worker goroutines consumed tasks, for range loop finishes when tasks channel buffer is empty. It won’t throw deadlock error as tasks channel was closed.
    • Sometimes, all worker goroutines can be sleeping, so main goroutine will wake up and works until results channel buffer is again empty.
    • After all worker goroutines died, main goroutine will regain control and print remaining results from results channel and continue its execution.
    https://play.golang.org/p/0rRfchn7sL1

    ☛ Mutex

    Mutex is one of the easiest concepts in Go. But before I explain it, let’s first understand what a race condition is. goroutines have their independent stack and hence they don’t share any data between them. But there might be conditions where some data in heap is shared between multiple goroutines. In that case, multiple goroutines are trying to manipulate data at the same memory location resulting in unexpected results. I will show you one simple example.

    https://play.golang.org/p/MQNepChxiEa
    value of i after 1000 operations is 937
    • (2) increment value of i by 1
    • (3) update value of i with new value
    https://play.golang.org/p/xVFAX_0Uig8
    value of i after 1000 operations is 1000

    Concurrency Patterns

    There are tons of ways concurrency makes our day to day programming life easier. Following are few concepts and methodologies using which we can make programs faster and reliable.

    Generator

    Using channels, we can implement a generator in a much better way. If a generator is computationally expensive, then we might as well do the generation of data concurrently. That way, the program doesn’t have to wait until all data is generated. For example, generating a fibonacci series.

    https://play.golang.org/p/1_2MDeqQ3o5

    fan-in & fan-out

    fan-in is a multiplexing strategy where the inputs of several channels are combined to produce an output channel. fan-out is demultiplexing strategy where a single channel is split into multiple channels.

    https://play.golang.org/p/hATZmb6P1-u
  • 相关阅读:
    ANDROID笔记:shape的简单使用
    ANDROID笔记:根据长宽实现图片压缩
    ANDROID笔记:PopupMenu的用法
    ANDROID笔记:AdapterContextMenuInfo在ListView中的用法
    ANDROID笔记:利用XML布局文件生成Menu
    ANDROID笔记:ContextMenu的用法
    ANDROID笔记:JSON和Adapter的使用
    ANDROID笔记:Dialog的几种用法
    ANDROID笔记:AdapterViewFlipper和ViewFlipper的简单使用
    #2020征文-开发板# 用鸿蒙开发AI应用(一)硬件篇
  • 原文地址:https://www.cnblogs.com/oxspirt/p/12013285.html
Copyright © 2020-2023  润新知