add blocking select
Этот коммит содержится в:
родитель
fa25fa1b0c
коммит
cdff0bd3ee
4 изменённых файлов: 289 добавлений и 45 удалений
|
@ -136,7 +136,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||||
recvbuf := llvm.Undef(c.i8ptrType)
|
recvbuf := llvm.Undef(c.i8ptrType)
|
||||||
if hasReceives {
|
if hasReceives {
|
||||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
||||||
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
|
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
@ -146,7 +146,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||||
|
|
||||||
// Create the states slice (allocated on the stack).
|
// Create the states slice (allocated on the stack).
|
||||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
||||||
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
|
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
|
||||||
for i, state := range selectStates {
|
for i, state := range selectStates {
|
||||||
// Set each slice element to the appropriate channel.
|
// Set each slice element to the appropriate channel.
|
||||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||||
|
@ -161,19 +161,36 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||||
}, "select.states")
|
}, "select.states")
|
||||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||||
|
|
||||||
// Convert the 'blocking' flag on this select into a LLVM value.
|
|
||||||
blockingInt := uint64(0)
|
|
||||||
if expr.Blocking {
|
|
||||||
blockingInt = 1
|
|
||||||
}
|
|
||||||
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
|
|
||||||
|
|
||||||
// Do the select in the runtime.
|
// Do the select in the runtime.
|
||||||
results := c.createRuntimeCall("chanSelect", []llvm.Value{
|
var results llvm.Value
|
||||||
recvbuf,
|
if expr.Blocking {
|
||||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
// Stack-allocate operation structures.
|
||||||
blockingValue,
|
// If these were simply created as a slice, they would heap-allocate.
|
||||||
}, "")
|
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||||
|
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||||
|
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||||
|
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
}, "select.block")
|
||||||
|
|
||||||
|
results = c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||||
|
recvbuf,
|
||||||
|
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||||
|
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
||||||
|
}, "select.result")
|
||||||
|
|
||||||
|
// Terminate the lifetime of the operation structures.
|
||||||
|
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
||||||
|
} else {
|
||||||
|
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||||
|
recvbuf,
|
||||||
|
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||||
|
}, "select.result")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate the lifetime of the states alloca.
|
||||||
|
c.emitLifetimeEnd(statesI8, statesSize)
|
||||||
|
|
||||||
// The result value does not include all the possible received values,
|
// The result value does not include all the possible received values,
|
||||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
// because we can't load them in advance. Instead, the *ssa.Extract
|
||||||
|
|
|
@ -37,11 +37,86 @@ func chanDebug(ch *channel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// channelBlockedList is a list of channel operations on a specific channel which are currently blocked.
|
||||||
|
type channelBlockedList struct {
|
||||||
|
// next is a pointer to the next blocked channel operation on the same channel.
|
||||||
|
next *channelBlockedList
|
||||||
|
|
||||||
|
// t is the task associated with this channel operation.
|
||||||
|
// If this channel operation is not part of a select, then the pointer field of the state holds the data buffer.
|
||||||
|
// If this channel operation is part of a select, then the pointer field of the state holds the recieve buffer.
|
||||||
|
// If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure.
|
||||||
|
t *task
|
||||||
|
|
||||||
|
// s is a pointer to the channel select state corresponding to this operation.
|
||||||
|
// This will be nil if and only if this channel operation is not part of a select statement.
|
||||||
|
// If this is a send operation, then the send buffer can be found in this select state.
|
||||||
|
s *chanSelectState
|
||||||
|
|
||||||
|
// allSelectOps is a slice containing all of the channel operations involved with this select statement.
|
||||||
|
// Before resuming the task, all other channel operations on this select statement should be canceled by removing them from their corresponding lists.
|
||||||
|
allSelectOps []channelBlockedList
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove takes the current list of blocked channel operations and removes the specified operation.
|
||||||
|
// This returns the resulting list, or nil if the resulting list is empty.
|
||||||
|
// A nil receiver is treated as an empty list.
|
||||||
|
func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList {
|
||||||
|
if b == old {
|
||||||
|
return b.next
|
||||||
|
}
|
||||||
|
c := b
|
||||||
|
for ; c != nil && c.next != old; c = c.next {
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
c.next = old.next
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// detatch removes all other channel operations that are part of the same select statement.
|
||||||
|
// If the input is not part of a select statement, this is a no-op.
|
||||||
|
// This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice.
|
||||||
|
func (b *channelBlockedList) detach() {
|
||||||
|
if b.allSelectOps == nil {
|
||||||
|
// nothing to do
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, v := range b.allSelectOps {
|
||||||
|
// cancel all other channel operations that are part of this select statement
|
||||||
|
if &b.allSelectOps[i] == b {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v.s.ch == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.s.ch.blocked = v.s.ch.blocked.remove(&b.allSelectOps[i])
|
||||||
|
if v.s.ch.blocked == nil {
|
||||||
|
if v.s.value == nil {
|
||||||
|
// recv operation
|
||||||
|
if v.s.ch.state != chanStateClosed {
|
||||||
|
v.s.ch.state = chanStateEmpty
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// send operation
|
||||||
|
if v.s.ch.bufUsed == 0 {
|
||||||
|
// unbuffered channel
|
||||||
|
v.s.ch.state = chanStateEmpty
|
||||||
|
} else {
|
||||||
|
// buffered channel
|
||||||
|
v.s.ch.state = chanStateBuf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chanDebug(v.s.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type channel struct {
|
type channel struct {
|
||||||
elementSize uintptr // the size of one value in this channel
|
elementSize uintptr // the size of one value in this channel
|
||||||
bufSize uintptr // size of buffer (in elements)
|
bufSize uintptr // size of buffer (in elements)
|
||||||
state chanState
|
state chanState
|
||||||
blocked *task
|
blocked *channelBlockedList
|
||||||
bufHead uintptr // head index of buffer (next push index)
|
bufHead uintptr // head index of buffer (next push index)
|
||||||
bufTail uintptr // tail index of buffer (next pop index)
|
bufTail uintptr // tail index of buffer (next pop index)
|
||||||
bufUsed uintptr // number of elements currently in buffer
|
bufUsed uintptr // number of elements currently in buffer
|
||||||
|
@ -58,6 +133,63 @@ func chanMake(elementSize uintptr, bufSize uintptr) *channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resumeRX resumes the next receiver and returns the destination pointer.
|
||||||
|
// If the ok value is true, then the caller is expected to store a value into this pointer.
|
||||||
|
func (ch *channel) resumeRX(ok bool) unsafe.Pointer {
|
||||||
|
// pop a blocked goroutine off the stack
|
||||||
|
var b *channelBlockedList
|
||||||
|
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||||
|
|
||||||
|
// get destination pointer
|
||||||
|
dst := b.t.state().ptr
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// the result value is zero
|
||||||
|
memzero(dst, ch.elementSize)
|
||||||
|
b.t.state().data = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.s != nil {
|
||||||
|
// tell the select op which case resumed
|
||||||
|
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||||
|
|
||||||
|
// detach associated operations
|
||||||
|
b.detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
// push task onto runqueue
|
||||||
|
runqueuePushBack(b.t)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// resumeTX resumes the next sender and returns the source pointer.
|
||||||
|
// The caller is expected to read from the value in this pointer before yielding.
|
||||||
|
func (ch *channel) resumeTX() unsafe.Pointer {
|
||||||
|
// pop a blocked goroutine off the stack
|
||||||
|
var b *channelBlockedList
|
||||||
|
b, ch.blocked = ch.blocked, ch.blocked.next
|
||||||
|
|
||||||
|
// get source pointer
|
||||||
|
src := b.t.state().ptr
|
||||||
|
|
||||||
|
if b.s != nil {
|
||||||
|
// use state's source pointer
|
||||||
|
src = b.s.value
|
||||||
|
|
||||||
|
// tell the select op which case resumed
|
||||||
|
b.t.state().ptr = unsafe.Pointer(b.s)
|
||||||
|
|
||||||
|
// detach associated operations
|
||||||
|
b.detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
// push task onto runqueue
|
||||||
|
runqueuePushBack(b.t)
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
// push value to end of channel if space is available
|
// push value to end of channel if space is available
|
||||||
// returns whether there was space for the value in the buffer
|
// returns whether there was space for the value in the buffer
|
||||||
func (ch *channel) push(value unsafe.Pointer) bool {
|
func (ch *channel) push(value unsafe.Pointer) bool {
|
||||||
|
@ -151,12 +283,10 @@ func (ch *channel) trySend(value unsafe.Pointer) bool {
|
||||||
return false
|
return false
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// unblock reciever
|
// unblock reciever
|
||||||
receiver := unblockChain(&ch.blocked, nil)
|
dst := ch.resumeRX(true)
|
||||||
|
|
||||||
// copy value to reciever
|
// copy value to reciever
|
||||||
receiverState := receiver.state()
|
memcpy(dst, value, ch.elementSize)
|
||||||
memcpy(receiverState.ptr, value, ch.elementSize)
|
|
||||||
receiverState.data = 1 // commaOk = true
|
|
||||||
|
|
||||||
// change state to empty if there are no more receivers
|
// change state to empty if there are no more receivers
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
|
@ -191,9 +321,11 @@ func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
|
||||||
// try to pop the value directly from the buffer
|
// try to pop the value directly from the buffer
|
||||||
if ch.pop(value) {
|
if ch.pop(value) {
|
||||||
// unblock next sender if applicable
|
// unblock next sender if applicable
|
||||||
if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
if ch.blocked != nil {
|
||||||
|
src := ch.resumeTX()
|
||||||
|
|
||||||
// push sender's value into buffer
|
// push sender's value into buffer
|
||||||
ch.push(sender.state().ptr)
|
ch.push(src)
|
||||||
|
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
// last sender unblocked - update state
|
// last sender unblocked - update state
|
||||||
|
@ -207,10 +339,12 @@ func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, true
|
return true, true
|
||||||
} else if sender := unblockChain(&ch.blocked, nil); sender != nil {
|
} else if ch.blocked != nil {
|
||||||
// unblock next sender if applicable
|
// unblock next sender if applicable
|
||||||
|
src := ch.resumeTX()
|
||||||
|
|
||||||
// copy sender's value
|
// copy sender's value
|
||||||
memcpy(value, sender.state().ptr, ch.elementSize)
|
memcpy(value, src, ch.elementSize)
|
||||||
|
|
||||||
if ch.blocked == nil {
|
if ch.blocked == nil {
|
||||||
// last sender unblocked - update state
|
// last sender unblocked - update state
|
||||||
|
@ -294,7 +428,10 @@ func chanSend(ch *channel, value unsafe.Pointer) {
|
||||||
ch.state = chanStateSend
|
ch.state = chanStateSend
|
||||||
senderState := sender.state()
|
senderState := sender.state()
|
||||||
senderState.ptr = value
|
senderState.ptr = value
|
||||||
ch.blocked, senderState.next = sender, ch.blocked
|
ch.blocked = &channelBlockedList{
|
||||||
|
next: ch.blocked,
|
||||||
|
t: sender,
|
||||||
|
}
|
||||||
chanDebug(ch)
|
chanDebug(ch)
|
||||||
yield()
|
yield()
|
||||||
senderState.ptr = nil
|
senderState.ptr = nil
|
||||||
|
@ -320,8 +457,11 @@ func chanRecv(ch *channel, value unsafe.Pointer) bool {
|
||||||
receiver := getCoroutine()
|
receiver := getCoroutine()
|
||||||
ch.state = chanStateRecv
|
ch.state = chanStateRecv
|
||||||
receiverState := receiver.state()
|
receiverState := receiver.state()
|
||||||
receiverState.ptr, receiverState.data = value, 0
|
receiverState.ptr, receiverState.data = value, 1
|
||||||
ch.blocked, receiverState.next = receiver, ch.blocked
|
ch.blocked = &channelBlockedList{
|
||||||
|
next: ch.blocked,
|
||||||
|
t: receiver,
|
||||||
|
}
|
||||||
chanDebug(ch)
|
chanDebug(ch)
|
||||||
yield()
|
yield()
|
||||||
ok := receiverState.data == 1
|
ok := receiverState.data == 1
|
||||||
|
@ -348,15 +488,9 @@ func chanClose(ch *channel) {
|
||||||
runtimePanic("close channel during send")
|
runtimePanic("close channel during send")
|
||||||
case chanStateRecv:
|
case chanStateRecv:
|
||||||
// unblock all receivers with the zero value
|
// unblock all receivers with the zero value
|
||||||
for rx := unblockChain(&ch.blocked, nil); rx != nil; rx = unblockChain(&ch.blocked, nil) {
|
ch.state = chanStateClosed
|
||||||
// get receiver state
|
for ch.blocked != nil {
|
||||||
state := rx.state()
|
ch.resumeRX(false)
|
||||||
|
|
||||||
// store the zero value
|
|
||||||
memzero(state.ptr, ch.elementSize)
|
|
||||||
|
|
||||||
// set the comma-ok value to false (channel closed)
|
|
||||||
state.data = 0
|
|
||||||
}
|
}
|
||||||
case chanStateEmpty, chanStateBuf:
|
case chanStateEmpty, chanStateBuf:
|
||||||
// Easy case. No available sender or receiver.
|
// Easy case. No available sender or receiver.
|
||||||
|
@ -371,7 +505,60 @@ func chanClose(ch *channel) {
|
||||||
//
|
//
|
||||||
// TODO: do this in a round-robin fashion (as specified in the Go spec) instead
|
// TODO: do this in a round-robin fashion (as specified in the Go spec) instead
|
||||||
// of picking the first one that can proceed.
|
// of picking the first one that can proceed.
|
||||||
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool) (uintptr, bool) {
|
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelBlockedList) (uintptr, bool) {
|
||||||
|
if selected, ok := tryChanSelect(recvbuf, states); selected != ^uintptr(0) {
|
||||||
|
// one channel was immediately ready
|
||||||
|
return selected, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct blocked operations
|
||||||
|
for i, v := range states {
|
||||||
|
ops[i] = channelBlockedList{
|
||||||
|
next: v.ch.blocked,
|
||||||
|
t: getCoroutine(),
|
||||||
|
s: &states[i],
|
||||||
|
allSelectOps: ops,
|
||||||
|
}
|
||||||
|
v.ch.blocked = &ops[i]
|
||||||
|
if v.value == nil {
|
||||||
|
// recv
|
||||||
|
switch v.ch.state {
|
||||||
|
case chanStateEmpty:
|
||||||
|
v.ch.state = chanStateRecv
|
||||||
|
case chanStateRecv:
|
||||||
|
// already in correct state
|
||||||
|
default:
|
||||||
|
runtimePanic("invalid channel state")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// send
|
||||||
|
switch v.ch.state {
|
||||||
|
case chanStateEmpty:
|
||||||
|
v.ch.state = chanStateSend
|
||||||
|
case chanStateSend:
|
||||||
|
// already in correct state
|
||||||
|
case chanStateBuf:
|
||||||
|
// already in correct state
|
||||||
|
default:
|
||||||
|
runtimePanic("invalid channel state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chanDebug(v.ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose rx buffer
|
||||||
|
getCoroutine().state().ptr = recvbuf
|
||||||
|
getCoroutine().state().data = 1
|
||||||
|
|
||||||
|
// wait for one case to fire
|
||||||
|
yield()
|
||||||
|
|
||||||
|
// figure out which one fired and return the ok value
|
||||||
|
return (uintptr(getCoroutine().state().ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), getCoroutine().state().data != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryChanSelect is like chanSelect, but it does a non-blocking select operation.
|
||||||
|
func tryChanSelect(recvbuf unsafe.Pointer, states []chanSelectState) (uintptr, bool) {
|
||||||
// See whether we can receive from one of the channels.
|
// See whether we can receive from one of the channels.
|
||||||
for i, state := range states {
|
for i, state := range states {
|
||||||
if state.value == nil {
|
if state.value == nil {
|
||||||
|
@ -389,8 +576,5 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, blocking bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !blocking {
|
return ^uintptr(0), false
|
||||||
return ^uintptr(0), false
|
|
||||||
}
|
|
||||||
panic("unimplemented: blocking select")
|
|
||||||
}
|
}
|
||||||
|
|
52
testdata/channel.go
предоставленный
52
testdata/channel.go
предоставленный
|
@ -1,8 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// waitGroup is a small type reimplementing some of the behavior of sync.WaitGroup
|
// waitGroup is a small type reimplementing some of the behavior of sync.WaitGroup
|
||||||
|
@ -91,7 +91,7 @@ func main() {
|
||||||
println("sum(100):", sum)
|
println("sum(100):", sum)
|
||||||
|
|
||||||
// Test simple selects.
|
// Test simple selects.
|
||||||
go selectDeadlock() // cannot use waitGroup here - never terminates
|
go selectDeadlock() // cannot use waitGroup here - never terminates
|
||||||
wg.add(1)
|
wg.add(1)
|
||||||
go selectNoOp()
|
go selectNoOp()
|
||||||
wg.wait()
|
wg.wait()
|
||||||
|
@ -117,11 +117,10 @@ func main() {
|
||||||
ch = make(chan int)
|
ch = make(chan int)
|
||||||
wg.add(1)
|
wg.add(1)
|
||||||
go func(ch chan int) {
|
go func(ch chan int) {
|
||||||
|
runtime.Gosched()
|
||||||
ch <- 55
|
ch <- 55
|
||||||
wg.done()
|
wg.done()
|
||||||
}(ch)
|
}(ch)
|
||||||
// not defined behavior, but we cant really fix this until select has been fixed
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
select {
|
select {
|
||||||
case make(chan int) <- 3:
|
case make(chan int) <- 3:
|
||||||
println("unreachable")
|
println("unreachable")
|
||||||
|
@ -147,7 +146,6 @@ func main() {
|
||||||
ch = make(chan int)
|
ch = make(chan int)
|
||||||
wg.add(1)
|
wg.add(1)
|
||||||
go fastreceiver(ch)
|
go fastreceiver(ch)
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
select {
|
select {
|
||||||
case ch <- 235:
|
case ch <- 235:
|
||||||
println("select send")
|
println("select send")
|
||||||
|
@ -188,6 +186,50 @@ func main() {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
println("hybrid buffered channel recieve:", count)
|
println("hybrid buffered channel recieve:", count)
|
||||||
|
|
||||||
|
// test blocking selects
|
||||||
|
ch = make(chan int)
|
||||||
|
sch1 := make(chan int)
|
||||||
|
sch2 := make(chan int)
|
||||||
|
sch3 := make(chan int)
|
||||||
|
wg.add(3)
|
||||||
|
go func() {
|
||||||
|
defer wg.done()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
sch1 <- 1
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.done()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
sch2 <- 2
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.done()
|
||||||
|
// merge sch2 and sch3 into ch
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
var v int
|
||||||
|
select {
|
||||||
|
case v = <-sch1:
|
||||||
|
case v = <-sch2:
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case sch3 <- v:
|
||||||
|
panic("sent to unused channel")
|
||||||
|
case ch <- v:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
sum = 0
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
select {
|
||||||
|
case sch3 <- sum:
|
||||||
|
panic("sent to unused channel")
|
||||||
|
case v := <-ch:
|
||||||
|
sum += v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.wait()
|
||||||
|
println("blocking select sum:", sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(ch chan<- int) {
|
func send(ch chan<- int) {
|
||||||
|
|
1
testdata/channel.txt
предоставленный
1
testdata/channel.txt
предоставленный
|
@ -31,3 +31,4 @@ closed buffered channel recieve: 3
|
||||||
closed buffered channel recieve: 4
|
closed buffered channel recieve: 4
|
||||||
closed buffered channel recieve: 0
|
closed buffered channel recieve: 0
|
||||||
hybrid buffered channel recieve: 2
|
hybrid buffered channel recieve: 2
|
||||||
|
blocking select sum: 3
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче