Open ckornher opened 4 years ago
@ckornher I'm not familiar with go lang, but select
looks like an actor. You can do something like this:
// Message types for actor
enum CounterMessages {
case increment, getCounter(CoPromise<Int>)
}
let actor = DispatchQueue.global().actor(of: CounterMessages.self) { receiver in
var counter = 0
for message in receiver {
switch message {
case .increment:
counter += 1
case .getCounter(let promise):
promise.success(counter)
}
}
}
DispatchQueue.concurrentPerform(iterations: 100_000) { _ in
actor.offer(.increment)
}
let promise = CoPromise<Int>()
promise.whenSuccess { print($0) }
actor.offer(.getCounter(promise))
actor.close()
In golang you can use select to read from multiple channels in the same coroutine. E.g.
for i := 0; i < n; i++ {
select {
case msgFromChannel1 := <-channel1:
println("received", msgFromChannel1)
case msgFromChannel2 := <-channel2:
println("received", msgFromChannel2)
}
}
@FabianTerhorst @ckornher Hi, it looks really great in golang. We can create some "select" channel to combine results from several channels. It may look something like this (may still need to think about which channel closes or cancels whom):
extension CoChannel {
func addSubchannel(_ channel: CoChannel) {
// Cancel subchannel on complete
whenComplete(channel.cancel)
// Offer value when receive result
channel.whenReceive { _ = $0.map(self.offer) }
}
func addSubchannel<T>(_ channel: CoChannel<T>, transformer: @escaping (T) -> Element) {
// Cancel subchannel on complete
whenComplete(channel.cancel)
// Offer value when receive result
channel.whenReceive { result in
guard let value = try? result.get() else { return }
self.offer(transformer(value))
}
}
}
Then the use may look like this:
enum Foo {
case caseA(Int), caseB(String)
}
let channelA: CoChannel<Int>
let channelB: CoChannel<String>
let selectChannel = CoChannel<Foo>()
selectChannel.addSubchannel(channelA, transformer: Foo.caseA)
selectChannel.addSubchannel(channelB, transformer: Foo.caseB)
DispatchQueue.global().startCoroutine {
for foo in selectChannel.makeIterator() {
switch foo {
case .caseA(let a):
print(a)
case .caseB(let b):
print(b)
}
}
}
We can create some "select" channel to combine results from several channels.
Yes, this would work except that I think that it suffers from the same problem that I ran into when I tried to use the library as-is. I could not avoid a case where the logic captures values from the "subchannels" while an element is being processed.
Golang never buffers elements within a select
and it is safe, for example, to fail the processing of an element and stop a "goroutine" without any other "future" elements being taken from channels in the select.
Golang's select is often used to have a measure of "out-of-band" control over a "goroutine" and buffering would defeat this sort of logic.
Golang select
applies a pseudo-random fairness algorithm for "subchannels" that have data. There would be advantages to having priority for channels in some situations, but fairness should be an option, at least.
Golang's select
has a default
case that executes if none of the "subchannels` has elements. It is rarely used, in my experience, and almost never within a loop, so it would be a "nice to have" feature IMO.
I do not see a way around something like a multi-channel await()
method. Pushing elements back to the front of a channel could re-order elements when multiple coroutines are reading a channel. This is common in Go's "pipeline" pattern.
@ckornher @FabianTerhorst Thank you for explanation. It seems that select
is really important functionality that I've missed. I need some free time to think how to implement it properly.
@ckornher I wonder what is a real-world use case for select
that cannot be implemented (or too error-prone to implement) using put/take. Could you maybe share some?
For educational purposes, I am implementing Go channels myself. So far I only have put and take operations, and for my simple use cases on project, it was enough so far. I want to keep no. of core operations minimum to keep the implementation correct. It seems like select
is not expressible in terms of put/take due to reordering. I wonder if it's necessary to have
Thanks
Edit: nevermind, I should just read Effective Go
Another feature request: Go has a
select
statement that facilitates the use of multiple channels simultaneously for timers, out-of-band control, etc.Are you planning to add something similar?
https://tour.golang.org/concurrency/5