通过Golang中的渠道进行双向通信

I have several functions that I want them to be executed atomically since they deal with sensitive data structures. Suppose the following scenario: There are two functions: lock(sth) and unlock(sth) that can be called anytime by a goroutine to lock or unlock sth in a global array. I was thinking about having a command channel so that goroutines send lock and unlock commands into the channel, and on the receive side of the channel, some kind of handler handles lock, unlock requests, sequentially, by grabbing commands from the channel. That's fine, but what if the handler wants to send the result back to the requester? Is it possible to do so use golang channels? I know that it is possible to use some kind of lock mechanism like mutex, but I was wondering if it's possible to use channels for such use-case? I saw somewhere that it is recommended to use channel instead of goland low-level lock structs.

In a single sentence:

In a channel with the capacity of 1, I want the receiver side to be able to reply back to the goroutine which sent the message.

or equivalently:

A goroutine sends something to a channel; the message is received by another goroutine and handled leading to some result; how does the sender become aware of the result?

The sync package includes a Mutex lock, sync.Mutex, which can be locked and unlocked from any goroutine in a threadsafe way. Instead of using a channel to send a command to lock something, how about just using a mutex lock from the sender?

mutex := new(sync.Mutex)
sensitiveData := make([]string, 0)
// when someone wants to operate on a sensitiveData,
// ...
mutex.Lock()
operate(sensitiveData)
mutex.Unlock()

When you say how does the sender become aware of the result, I think you're talking about how does the handler receive the result -- that would be with a chan. You can send data through channels.

Alternatively, if you just want to be aware, a semaphore, sync.WaitGroup might do the job. This struct can be Add()ed to, and then the sender can wg.Wait() until the handler calls wg.Done(), which will indicate to the sender (which is waiting) that the handler is done doing such and such.


If your question is about whether to use locks or channels, the wiki has a terse answer:

A common Go newbie mistake is to over-use channels and goroutines just because it's possible, and/or because it's fun. Don't be afraid to use a sync.Mutex if that fits your problem best. Go is pragmatic in letting you use the tools that solve your problem best and not forcing you into one style of code.

As a general guide, though:

Channel: passing ownership of data, distributing units of work, communicating async results
Mutex: caches, state


If you absolutely want to avoid anything but chans :), try not altering the sensitive array to begin with. Rather, use channels to send data to different goroutines, at each step processing the data, and then funneling the processed data into a final type goroutine. That is, avoid using an array at all and store the data in chans.

As the motto goes,

Do not communicate by sharing memory; instead, share memory by communicating.

If you want to prevent race conditions then sync primitives should work just fine, as described in @Nevermore's answer. It leaves the code much more readable and easier to reason about.

However, if you want channels to perform syncing for you, you can always try something like below:

// A global, shared channel used as a lock. Capacity of 1 allows for only
// one thread to access the protected resource at a time.
var lock = make(chan struct{}, 1)


// Operate performs the access/modification on the protected resource.
func Operate(f func() error) error {
    lock <- struct{}{}
    defer func() { <- lock }()
    return f()
}

To use this Operate, pass in a closure that accesses the protected resource.

// Some value that requires concurrent access.
var arr = []int{1, 2, 3, 4, 5}

// Used to sync up goroutines.
var wg sync.WaitGroup
wg.Add(len(arr))

for i := 0; i < len(arr); i++ {
    go func(j int) {
        defer wg.Done()

        // Access to arr remains protected.
        Operate(func () error {
            arr[j] *= 2
            return nil
        })

    }(i)
}
wg.Wait()

Working example: https://play.golang.org/p/Drh-yJDVNh

Or you can entirely bypass Operate and use lock directly for more readability:

go func(j int) {
    defer wg.Done()

    lock <- struct{}{}
    defer func() { <- lock }()

    arr[j] *= 2
}(i)

Working example: https://play.golang.org/p/me3K6aIoR7

As you can see, arr access is protected using a channel here.

The other questions have covered locking well, but I wanted to address the other part of the question around using channels to send a response back to a caller. There is a not-uncommon pattern in Go of sending a response channel with the request. For example, you might send commands to a handler over a channel; these commands would be a struct with implementation-specific details, and the struct would include a channel for sending the result back, typed to the result type. Each command sent would include a new channel, which the handler would use to send back the response, and then close. To illustrate:

type Command struct {
    // command parameters etc
    Results chan Result
}

type Result struct {
    // Whatever a result is in this case
}

var workQueue = make(chan Command)

// Example for executing synchronously
func Example(param1 string, param2 int) Result {
    workQueue <- Command{
        Param1: param1,
        Param2: param2,
        Results: make(chan Result),
    }
    return <- Results