Преглед изворни кода

document and test public api

Tao Wen пре 7 година
родитељ
комит
48aaeb112d
6 измењених фајлова са 86 додато и 0 уклоњено
  1. 7 0
      executor.go
  2. 2 0
      go_above_19.go
  3. 4 0
      go_below_19.go
  4. 18 0
      map_test.go
  5. 17 0
      unbounded_executor.go
  6. 38 0
      unbounded_executor_test.go

+ 7 - 0
executor.go

@@ -2,6 +2,13 @@ package concurrent
 
 import "context"
 
+// Executor replace go keyword to start a new goroutine
+// the goroutine should cancel itself if the context passed in has been cancelled
+// the goroutine started by the executor, is owned by the executor
+// we can cancel all executors owned by the executor just by stop the executor itself
+// however Executor interface does not Stop method, the one starting and owning executor
+// should use the concrete type of executor, instead of this interface.
 type Executor interface {
+	// Go starts a new goroutine controlled by the context
 	Go(handler func(ctx context.Context))
 }

+ 2 - 0
go_above_19.go

@@ -4,10 +4,12 @@ package concurrent
 
 import "sync"
 
+// Map is a wrapper for sync.Map introduced in go1.9
 type Map struct {
 	sync.Map
 }
 
+// NewMap creates a thread safe Map
 func NewMap() *Map {
 	return &Map{}
 }

+ 4 - 0
go_below_19.go

@@ -4,17 +4,20 @@ package concurrent
 
 import "sync"
 
+// Map implements a thread safe map for go version below 1.9 using mutex
 type Map struct {
 	lock sync.RWMutex
 	data map[interface{}]interface{}
 }
 
+// NewMap creates a thread safe map
 func NewMap() *Map {
 	return &Map{
 		data: make(map[interface{}]interface{}, 32),
 	}
 }
 
+// Load is same as sync.Map Load
 func (m *Map) Load(key interface{}) (elem interface{}, found bool) {
 	m.lock.RLock()
 	elem, found = m.data[key]
@@ -22,6 +25,7 @@ func (m *Map) Load(key interface{}) (elem interface{}, found bool) {
 	return
 }
 
+// Load is same as sync.Map Store
 func (m *Map) Store(key interface{}, elem interface{}) {
 	m.lock.Lock()
 	m.data[key] = elem

+ 18 - 0
map_test.go

@@ -0,0 +1,18 @@
+package concurrent_test
+
+import (
+	"testing"
+	"github.com/modern-go/concurrent"
+)
+
+func TestMap_Load(t *testing.T) {
+	m := concurrent.NewMap()
+	m.Store("hello", "world")
+	value, found := m.Load("hello")
+	if !found {
+		t.Fail()
+	}
+	if value != "world" {
+		t.Fail()
+	}
+}

+ 17 - 0
unbounded_executor.go

@@ -9,17 +9,23 @@ import (
 	"runtime/debug"
 )
 
+// LogInfo logs informational message, for example
+// which goroutine is still alive preventing the executor shutdown
 var LogInfo = func(event string, properties ...interface{}) {
 }
 
+// LogPanic logs goroutine panic
 var LogPanic = func(recovered interface{}, properties ...interface{}) interface{} {
 	fmt.Println(fmt.Sprintf("paniced: %v", recovered))
 	debug.PrintStack()
 	return recovered
 }
 
+// StopSignal will not be recovered, will propagate to upper level goroutine
 const StopSignal = "STOP!"
 
+// UnboundedExecutor is a executor without limits on counts of alive goroutines
+// it tracks the goroutine started by it, and can cancel them when shutdown
 type UnboundedExecutor struct {
 	ctx                   context.Context
 	cancel                context.CancelFunc
@@ -29,8 +35,12 @@ type UnboundedExecutor struct {
 
 // GlobalUnboundedExecutor has the life cycle of the program itself
 // any goroutine want to be shutdown before main exit can be started from this executor
+// GlobalUnboundedExecutor expects the main function to call stop
+// it does not magically knows the main function exits
 var GlobalUnboundedExecutor = NewUnboundedExecutor()
 
+// NewUnboundedExecutor creates a new UnboundedExecutor,
+// UnboundedExecutor can not be created by &UnboundedExecutor{}
 func NewUnboundedExecutor() *UnboundedExecutor {
 	ctx, cancel := context.WithCancel(context.TODO())
 	return &UnboundedExecutor{
@@ -41,6 +51,8 @@ func NewUnboundedExecutor() *UnboundedExecutor {
 	}
 }
 
+// Go starts a new goroutine and tracks its lifecycle.
+// Panic will be recovered and logged automatically, except for StopSignal
 func (executor *UnboundedExecutor) Go(handler func(ctx context.Context)) {
 	_, file, line, _ := runtime.Caller(1)
 	executor.activeGoroutinesMutex.Lock()
@@ -61,14 +73,19 @@ func (executor *UnboundedExecutor) Go(handler func(ctx context.Context)) {
 	}()
 }
 
+// Stop cancel all goroutines started by this executor without wait
 func (executor *UnboundedExecutor) Stop() {
 	executor.cancel()
 }
 
+// Stop cancel all goroutines started by this executor and
+// wait until all goroutines exited
 func (executor *UnboundedExecutor) StopAndWaitForever() {
 	executor.StopAndWait(context.Background())
 }
 
+// Stop cancel all goroutines started by this executor and wait.
+// Wait can be cancelled by the context passed in.
 func (executor *UnboundedExecutor) StopAndWait(ctx context.Context) {
 	executor.cancel()
 	for {

+ 38 - 0
unbounded_executor_test.go

@@ -0,0 +1,38 @@
+package concurrent
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+
+func ExampleUnboundedExecutor_Go() {
+	executor := NewUnboundedExecutor()
+	executor.Go(func(ctx context.Context) {
+		fmt.Println("abc")
+	})
+	time.Sleep(time.Second)
+	// output: abc
+}
+
+func ExampleUnboundedExecutor_StopAndWaitForever() {
+	executor := NewUnboundedExecutor()
+	executor.Go(func(ctx context.Context) {
+		everyMillisecond := time.NewTicker(time.Millisecond)
+		for {
+			select {
+			case <-ctx.Done():
+				fmt.Println("goroutine exited")
+				return
+			case <-everyMillisecond.C:
+				// do something
+			}
+		}
+	})
+	time.Sleep(time.Second)
+	executor.StopAndWaitForever()
+	fmt.Println("exectuor stopped")
+	// output:
+	// goroutine exited
+	// exectuor stopped
+}