runtime: add cheap atomic condition variable
Этот коммит содержится в:
родитель
b40f250530
коммит
ed9b97cc0d
3 изменённых файлов: 172 добавлений и 0 удалений
90
src/runtime/cond.go
Обычный файл
90
src/runtime/cond.go
Обычный файл
|
@ -0,0 +1,90 @@
|
||||||
|
// +build !scheduler.none
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"internal/task"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// notifiedPlaceholder is a placeholder task which is used to indicate that the condition variable has been notified.
|
||||||
|
var notifiedPlaceholder task.Task
|
||||||
|
|
||||||
|
// Cond is a simplified condition variable, useful for notifying goroutines of interrupts.
|
||||||
|
type Cond struct {
|
||||||
|
t *task.Task
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify sends a notification.
|
||||||
|
// If the condition variable already has a pending notification, this returns false.
|
||||||
|
func (c *Cond) Notify() bool {
|
||||||
|
for {
|
||||||
|
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
|
||||||
|
switch t {
|
||||||
|
case nil:
|
||||||
|
// Nothing is waiting yet.
|
||||||
|
// Apply the notification placeholder.
|
||||||
|
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), unsafe.Pointer(¬ifiedPlaceholder)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case ¬ifiedPlaceholder:
|
||||||
|
// The condition variable has already been notified.
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
// Unblock the waiting task.
|
||||||
|
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
|
||||||
|
runqueuePushBack(t)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll checks for a notification.
|
||||||
|
// If a notification is found, it is cleared and this returns true.
|
||||||
|
func (c *Cond) Poll() bool {
|
||||||
|
for {
|
||||||
|
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
|
||||||
|
switch t {
|
||||||
|
case nil:
|
||||||
|
// No notifications are present.
|
||||||
|
return false
|
||||||
|
case ¬ifiedPlaceholder:
|
||||||
|
// A notification arrived and there is no waiting goroutine.
|
||||||
|
// Clear the notification and return.
|
||||||
|
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// A task is blocked on the condition variable, which means it has not been notified.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a notification.
|
||||||
|
// If the condition variable was previously notified, this returns immediately.
|
||||||
|
func (c *Cond) Wait() {
|
||||||
|
cur := task.Current()
|
||||||
|
for {
|
||||||
|
t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
|
||||||
|
switch t {
|
||||||
|
case nil:
|
||||||
|
// Condition variable has not been notified.
|
||||||
|
// Block the current task on the condition variable.
|
||||||
|
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), nil, unsafe.Pointer(cur)) {
|
||||||
|
task.Pause()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case ¬ifiedPlaceholder:
|
||||||
|
// A notification arrived and there is no waiting goroutine.
|
||||||
|
// Clear the notification and return.
|
||||||
|
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("interrupt.Cond: condition variable in use by another goroutine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/runtime/cond_nosched.go
Обычный файл
38
src/runtime/cond_nosched.go
Обычный файл
|
@ -0,0 +1,38 @@
|
||||||
|
// +build scheduler.none
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "runtime/interrupt"
|
||||||
|
|
||||||
|
// Cond is a simplified condition variable, useful for notifying goroutines of interrupts.
|
||||||
|
type Cond struct {
|
||||||
|
notified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify sends a notification.
|
||||||
|
// If the condition variable already has a pending notification, this returns false.
|
||||||
|
func (c *Cond) Notify() bool {
|
||||||
|
i := interrupt.Disable()
|
||||||
|
prev := c.notified
|
||||||
|
c.notified = true
|
||||||
|
interrupt.Restore(i)
|
||||||
|
return !prev
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll checks for a notification.
|
||||||
|
// If a notification is found, it is cleared and this returns true.
|
||||||
|
func (c *Cond) Poll() bool {
|
||||||
|
i := interrupt.Disable()
|
||||||
|
notified := c.notified
|
||||||
|
c.notified = false
|
||||||
|
interrupt.Restore(i)
|
||||||
|
return notified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a notification.
|
||||||
|
// If the condition variable was previously notified, this returns immediately.
|
||||||
|
func (c *Cond) Wait() {
|
||||||
|
for !c.Poll() {
|
||||||
|
waitForEvents()
|
||||||
|
}
|
||||||
|
}
|
44
testdata/coroutines.go
предоставленный
44
testdata/coroutines.go
предоставленный
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -71,6 +72,8 @@ func main() {
|
||||||
startSimpleFunc(emptyFunc)
|
startSimpleFunc(emptyFunc)
|
||||||
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
|
||||||
|
testCond()
|
||||||
}
|
}
|
||||||
|
|
||||||
func acquire(m *sync.Mutex) {
|
func acquire(m *sync.Mutex) {
|
||||||
|
@ -127,3 +130,44 @@ type simpleFunc func()
|
||||||
|
|
||||||
func emptyFunc() {
|
func emptyFunc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCond() {
|
||||||
|
var cond runtime.Cond
|
||||||
|
go func() {
|
||||||
|
// Wait for the caller to wait on the cond.
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
|
||||||
|
// Notify the caller.
|
||||||
|
ok := cond.Notify()
|
||||||
|
if !ok {
|
||||||
|
panic("notification not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This notification will be buffered inside the cond.
|
||||||
|
ok = cond.Notify()
|
||||||
|
if !ok {
|
||||||
|
panic("notification not queued")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This notification should fail, since there is already one buffered.
|
||||||
|
ok = cond.Notify()
|
||||||
|
if ok {
|
||||||
|
panic("notification double-sent")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Verify that the cond has no pending notifications.
|
||||||
|
ok := cond.Poll()
|
||||||
|
if ok {
|
||||||
|
panic("unexpected early notification")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the goroutine spawned earlier to send a notification.
|
||||||
|
cond.Wait()
|
||||||
|
|
||||||
|
// The goroutine should have also queued a notification in the cond.
|
||||||
|
ok = cond.Poll()
|
||||||
|
if !ok {
|
||||||
|
panic("missing queued notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче