A simple coroutine library for Golang.(https://github.com/Freezerburn/go-coroutine)

package coroutine

import (
     "time"
     "sync"
     "log"
)

// The base struct that has all the functions necessary to operate on a coroutine. It is designed to be used in one of
// two ways:
// - As the argument to a func passed to one of the Start functions. It can then be used as a proxy for the
// coroutine function. e.g.: `msg := e.Recv()`
// - Embedded directly into another struct, so that the struct can call the coroutine functions as if they were its
// own. e.g.: `type A struct { coroutine.Embeddable }` The struct that embeds the coroutine struct can then
// implement the Start method on the Starter interface and an instance of it can be passed directly to one of the
// Start functions. Embeddable MUST be embedded as a non-pointer, and the struct embedding it MUST be used as a
// pointer.
type Embeddable struct {
    id uint64
    name string
    waitTimer *time.Timer
    receiver chan bool
    receiveTimer *time.Timer
    mailbox [] interface{}
    mailboxLock sync.Mutex
    running bool
}

// Pauses execution of this coroutine for the given duration to allow other coroutines to run.
//
// If this coroutine has been stopped by external code using the Ref returned by all Start functions, then it will
// immediately stop, and no further code outside of deferred functions will be executed in this coroutine.
func (e *Embeddable) Pause(d time.Duration) {
     if !e.running {
         // Every coroutine is wrapped in a function that recovers from a panic, so this is guaranteed to immediately
         // stop execution of the coroutine completely without stopping the rest of the program.
         panic(Stop{})
    }

     resetTimer(e.waitTimer, d)
    <-e.waitTimer.C

     // Since there's a period of time that this is doing nothing, there's a chance that external code could stop
     // this coroutine while it's paused. So we check that before returning control to the coroutine.
     if !e.running {
         panic(Stop{})
    }
}

// Instead of constructing a new Timer object, which can be expensive if done frequently, we reset an existing one
// to a given duration immediately before using it.
func resetTimer(t *time.Timer, d time.Duration) {
     if !t. Stop() {
         // I've seen cases where Stop returns false, but there isn't an item in the channel, causing the receive to
         // hang forever.
         select {
         case <-t.C:
         default:
        }
    }
    t. Reset(d)
}

// Checks the mailbox for any sent messages. If none are in the mailbox, this function will halt the coroutine until
// one gets sent.
//
// If this coroutine has been stopped by external code using the Ref returned by all Start functions, then it will
// immediately stop, and no further code outside of deferred functions will be executed in this coroutine.
func (e *Embeddable) Recv() interface{} {
     if !e.running {
         panic(Stop{})
    }

    e.mailboxLock. Lock()
     if len(e.mailbox) == 0 {
        e.mailboxLock. Unlock()
        <-e.receiver

         if !e.running {
             panic(Stop{})
        }

        e.mailboxLock. Lock()
    }

     r := e.mailbox[ 0]
     e.mailbox = e.mailbox[ 1:]
    e.mailboxLock. Unlock()
     return r
}

// Checks if the mailbox contains anything. If it does, that value and true are returned. If it doesn't, the
// coroutine will pause for up to duration time. If a value is put into the mailbox within that time, that value and
// true are returned. If nothing was put into the mailbox during that time, nil and false are returned.
//
// If this coroutine has been stopped by external code using the Ref returned by all Start functions, then it will
// immediately stop, and no further code outside of deferred functions will be executed in this coroutine.
func (e *Embeddable) RecvFor(d time.Duration) ( interface{}, bool) {
     if !e.running {
         panic(Stop{})
    }

     if d <= 0 {
         return e. RecvImmediate()
    }

    e.mailboxLock. Lock()
     if len(e.mailbox) == 0 {
        e.mailboxLock. Unlock()

         resetTimer(e.receiveTimer, d)
         select {
         case <-e.receiver:
         case <-e.receiveTimer.C:
        }

         if !e.running {
             panic(Stop{})
        }

        e.mailboxLock. Lock()
    }

     if len(e.mailbox) == 0 {
        e.mailboxLock. Unlock()
         return nil, false
    }

     r := e.mailbox[ 0]
     e.mailbox = e.mailbox[ 1:]
    e.mailboxLock. Unlock()
     return r, true
}

// Checks if the mailbox contains anything. If it doesn't, nil and false are returned. If something is in the mailbox,
// that value and true are returned. The found value is removed from the mailbox.
//
// If this coroutine has been stopped by external code using the Ref returned by all Start functions, then it will
// immediately stop, and no further code outside of deferred functions will be executed in this coroutine.
func (e *Embeddable) RecvImmediate() ( interface{}, bool) {
     if !e.running {
         panic(Stop{})
    }

    e.mailboxLock. Lock()
     defer e.mailboxLock. Unlock()
     if len(e.mailbox) == 0 {
         return nil, false
    }

     r := e.mailbox[ 0]
     e.mailbox = e.mailbox[ 1:]
     return r, true
}

// Immediately stop this coroutine. No more code in the coroutine will run, so be sure to do any cleanup work before
// calling this function, or have a deferred function that will do your cleanup work.
func (e *Embeddable) Stop() {
     if !e.running {
        log. Printf( "Coroutine [%v / %s] attempted to stop itself when it isn't running, possible bug found.",
            e.id, e.name)
    }
     e.running = false
     panic(Stop{})
}


package coroutine

import (
     "log"
)

// Simple reference to a coroutine. Allows external code to send messages to that coroutine, stop it, and check
// various bits of data about it.
type Ref interface {
     Send(v interface{})
     Running() bool
     Name() string
     Id() uint64
     Stop()
}

// Separate struct from the Embeddable coroutine so that the Stop function can behave differently for external code
// versus internal to the coroutine.
type embeddableRef struct {
    e *Embeddable
}

// Puts a message into the mailbox of the coroutine this references.
func (r *embeddableRef) Send(v interface{}) {
    r.e.mailboxLock. Lock()
     r.e.mailbox = append(r.e.mailbox, v)
    r.e.mailboxLock. Unlock()

     // If the coroutine is waiting on the mailbox, let it know. Otherwise continue immediately so the sender
     // doesn't get blocked.
     select {
     case r.e.receiver <- true:
     default:
    }
}

// Whether or not the coroutine this references is still running.
func (r *embeddableRef) Running() bool {
     return r.e.running
}

// The name given to the coroutine this references at start time. If no name was given, a generic name is assigned.
func (r *embeddableRef) Name() string {
     return r.e.name
}

// The unique ID of the coroutine this references.
func (r *embeddableRef) Id() uint64 {
     return r.e.id
}

// Stops the coroutine this references. Will not immediately halt execution of the coroutine, but when it calls any
// of the methods on the Embeddable struct, execution will halt at that point. So if it's in a tight loop, that
// loop will finish.
func (r *embeddableRef) Stop() {
     if !r.e.running {
        log. Printf( "Coroutine [%v / %s] attempted to be stopped when it isn't running, possible bug found.",
            r.e.id, r.e.name)
         return
    }

     r.e.running = false
     // If the coroutine is in the middle of attempting to receive something, immediately cause it to stop attempting
     // to receive so it can detect that it needs to stop.
     select {
     case r.e.receiver <- true:
     default:
    }
}

package coroutine

import (
     "sync"
     "time"
)

// The type that is passed to panic whenever a coroutine is stopping itself. If a coroutine function has a
// deferred function which calls recover, this should be checked for to be ignored. If the deferred functions which
// recovers would cause a panic, and this was the value that came from the return value of recover, this should be
// what is given to panic to continue normal execution of the program.
type Stop struct {
}

func (s *Stop) Error() string {
     return "coroutine stop"
}

// Signature of the func that can be started as a coroutine. Receives the embeddable struct so that the func can
// call methods on it to act as a coroutine.
type Function func(embeddable *Embeddable)
type Starter interface {
     Start()
     Embedded() *Embeddable
}

const (
     defaultName = "Default Coroutine Name"
)

var (
     nextId uint64 = 1
     nextIdLock sync.Mutex
)

func StartFunc(f Function) Ref {
     return StartFuncName(defaultName, f)
}

func StartFuncName(name string, f Function) Ref {
     next := &Embeddable{
        name: name,
        waitTimer: time. NewTimer( 0),
        receiver: make( chan bool),
        receiveTimer: time. NewTimer( 0),
        running: true,
    }

    nextIdLock. Lock()
     next.id = nextId
    nextId++
    nextIdLock. Unlock()

     go func() {
         defer func() {
             // Ensure external code will know that this coroutine is stopped if the program doesn't end due to the
             // panic.
             next.running = false
             // Close down all the coroutine's resources.
            next.waitTimer. Stop()
            next.receiveTimer. Stop()
             close(next.receiver)

             if e := recover(); e != nil {
                 if _, ok := e.(Stop); ok {
                     // Stop requested for this coroutine, so we just let the goroutine end.
                } else {
                     // Repanic since it came from code that isn't part of the coroutine library.
                     panic(e)
                }
            }
        }()

         f(next)
    }()

     return &embeddableRef{next}
}

func Start(s Starter) Ref {
     return StartName(defaultName, s)
}

func (e *Embeddable) Embedded() *Embeddable {
     return e
}

func StartName(name string, s Starter) Ref {
     e := s. Embedded()
     e.name = name
     e.waitTimer = time. NewTimer( 0)
     e.receiver = make( chan bool)
     e.receiveTimer = time. NewTimer( 0)
     e.running = true

    nextIdLock. Lock()
     e.id = nextId
    nextId++
    nextIdLock. Unlock()

     go func() {
         defer func() {
             // Ensure external code will know that this coroutine is stopped if the program doesn't end due to the
             // panic.
             e.running = false
             // Close down all the coroutine's resources.
            e.waitTimer. Stop()
            e.receiveTimer. Stop()
             close(e.receiver)

             if e := recover(); e != nil {
                 if _, ok := e.(Stop); ok {
                     // Stop requested for this coroutine, so we just let the goroutine end.
                } else {
                     // Repanic since it came from code that isn't part of the coroutine library.
                     panic(e)
                }
            }
        }()

        s. Start()
    }()

     return &embeddableRef{e}
}


Example

type MyCoroutine struct {
    line int
    coroutine.Embeddable
}

func (c *MyCoroutine) Start() {
    for {
        switch msg := c.Recv().(type) {
        case int:
            fmt.Printf("Message %v: Received integer [%v]\n", c.line, msg)
        case string:
            fmt.Printf("Message %v: Received string [%s]\n", c.line, msg)
        }
        c.line++
    }

    fmt.Println("This never prints")
}

func main() {
    r := coroutine.Start(&MyCoroutine{})
    for i := 0; i < 100; i++ {
        r.Send(i);
        r.Send("msg " + strconv.Itoa(i))
    }
    time.Sleep(5 * time.Millisecond)
    r.Stop()
    time.Sleep(5 * time.Millisecond)
    fmt.Println("All done. All messages should have been printed, but not the println at the end of Start.")
}

猜你喜欢

转载自blog.csdn.net/qq_31967569/article/details/81015116