Skip to content

Commit 109c8f5

Browse files
committed
schedule: add block/wake function
Background: channel is designed to be a data transfer pipe between go routines. A scenario in gVisor is: block/wake a G (stands for a task) by using channel, wherein there's no need for data transfer. Channel is relatively too heavy for this case. Channel manages Gs in a list, and has capacity. When the channel is full, the sender will bock, and the channel is clear, the receiver is blocked. The receiver pushed to chan list, then schedule to run next. The sender uses goready to wake Gs in the chan list. A summary on our idea: introduce a new set of APIs for goroutine wake/block. Details: We propose three new APIs: GetG() - used to get the address of goroutine. By address, find the goroutine G easier at go program. WakeG() - can be used to wake one G, which can be in running/blocked status. BlockG() - can be used to block goroutine by itself. How we use this in gVisor: GuhuangLS/gvisor@97e0e6c In futex()/epoll_wait(), we can modify it to use the new mechanism for block and wake. Between sentry and go runtime, we maintain the status of task Gs. Let's use futex as an example, add running status at goruntime, NoWake,Waked,Blocked. At sentry, one task/G can use BlockG() to block, like <-chan. Other tasks/Gs can use WakeG() to wake the task/G which is blocked by BlockG() , like chan <-. Based on a basic prototype of Go and gVisor, we use the program in google/gvisor#2033(comment) as the test program. We can see 22% improvement by test case: google/gvisor#2033. Signed-off-by: liushi <[email protected]>
1 parent c89f122 commit 109c8f5

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

src/runtime/block_wake_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2013 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Futex is only available on DragonFly BSD, FreeBSD and Linux.
6+
// The race detector emits calls to split stack functions so it breaks
7+
// the test.
8+
9+
// +build dragonfly freebsd linux
10+
// +build !race
11+
12+
package runtime_test
13+
14+
import (
15+
"runtime"
16+
"testing"
17+
"time"
18+
)
19+
20+
var testG []uintptr
21+
22+
func TestBlockWakeG(t *testing.T) {
23+
for i := 0; i < 1; i++ {
24+
go func() {
25+
testG = append(testG, runtime.GetG())
26+
runtime.BlockG()
27+
runtime.ClearGStatus()
28+
}()
29+
}
30+
<-time.After(time.Second)
31+
32+
for _, g := range testG {
33+
runtime.WakeG(g)
34+
}
35+
<-time.After(time.Second)
36+
}

src/runtime/proc.go

+54
Original file line numberDiff line numberDiff line change
@@ -3284,6 +3284,60 @@ func injectglist(glist *gList) {
32843284
}
32853285
}
32863286

3287+
const (
3288+
gStatusNoWake = 0
3289+
gStatusWaked = 1
3290+
gStatusBlocked = 2
3291+
)
3292+
3293+
func GetG() uintptr {
3294+
gp := getg()
3295+
return uintptr(unsafe.Pointer(gp))
3296+
}
3297+
3298+
func ClearGStatus() {
3299+
lock(&sched.wakeLock)
3300+
gp := getg()
3301+
gp.wakeStatus = gStatusNoWake
3302+
unlock(&sched.wakeLock)
3303+
}
3304+
3305+
func WakeG(g uintptr) {
3306+
gp := guintptr(g).ptr()
3307+
if gp == nil {
3308+
return
3309+
}
3310+
lock(&sched.wakeLock)
3311+
if gp.wakeStatus != gStatusBlocked {
3312+
gp.wakeStatus = gStatusWaked
3313+
unlock(&sched.wakeLock)
3314+
return
3315+
}
3316+
gp.wakeStatus = gStatusNoWake
3317+
unlock(&sched.wakeLock)
3318+
goready(gp, 1)
3319+
}
3320+
3321+
func gosched_block(gp *g) {
3322+
dropg()
3323+
lock(&sched.wakeLock)
3324+
if gp.wakeStatus == gStatusWaked {
3325+
gp.wakeStatus = gStatusNoWake
3326+
unlock(&sched.wakeLock)
3327+
casgstatus(gp, _Grunning, _Grunnable)
3328+
execute(gp, false) // Schedule it back, never returns.
3329+
return
3330+
}
3331+
gp.wakeStatus = gStatusBlocked
3332+
casgstatus(gp, _Grunning, _Gwaiting)
3333+
unlock(&sched.wakeLock)
3334+
schedule()
3335+
}
3336+
3337+
func BlockG() {
3338+
mcall(gosched_block)
3339+
}
3340+
32873341
// One round of scheduler: find a runnable goroutine and execute it.
32883342
// Never returns.
32893343
func schedule() {

src/runtime/runtime2.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ type g struct {
431431
// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
432432
// closure in the runtime is forbidden.
433433
param unsafe.Pointer
434+
wakeStatus uint32
434435
atomicstatus uint32
435436
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
436437
goid int64
@@ -754,7 +755,8 @@ type schedt struct {
754755
lastpoll uint64 // time of last network poll, 0 if currently polling
755756
pollUntil uint64 // time to which current poll is sleeping
756757

757-
lock mutex
758+
lock mutex
759+
wakeLock mutex
758760

759761
// When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be
760762
// sure to call checkdead().

0 commit comments

Comments
 (0)