1. 程式人生 > >關於go語言,使用channel和timer定時執行以及優雅遇到的坑

關於go語言,使用channel和timer定時執行以及優雅遇到的坑

 問題描述:

1、我在主程序中,通過go啟動了兩個協程來處理labor的執行。主程序定位為g0,兩個協程分別定義為g1和g2

2、同時在主程序中,通過執行 runConfigChangedMonitor 來定時監控配置檔案是否發生變化,如果發生變化就退出

3、退出方法為Shutdown(),在該方法內部,呼叫labor的Shutdown()來關閉labor的執行,之後通過向chan bool channel設定一個

bool值來嘗試退出

4、但是在Shutdown()方法中,

g1和g2的Shutdown()能順利的執行

但是g0的如下這條語句的執行被hang住了

o.mQuit <- true

原因是:

1、預設初始化時使用的chan bool是無快取channel

2、當在主協程g0內部向一個無快取channel設定值的時候,預設會被hang住,除非本協程內部能從channel消費資料。但是由於設定和和消費都是同一個協程,所以無快取情況下,會出現hang的情況

3、在g0設定g1和g2的時候,都沒有問題(即便g1和g2協程內部也是定義的無快取bool channel,但是設定和消費是兩個不同的協程)

解決方案:

1、要麼設定的時候,採用go的匿名方法,通過go 執行匿名方法來實現

2、要麼將bool channel 初始化為帶快取的channel也可以解決

主要程式碼如下:

type LaborManager struct {
    mLaborConfs map[string]*LaborConf
    mConfigMD5  string
    mLabors     map[string]ILabor
    mTjApi      IDataaccess
    mFileReader info_reader.IFileReader
    mQuit       chan bool
    mTicker     *time.Ticker
    mLogger     tj.Logger
}

func NewLaborManager(tjApi IDataaccess, fileReader info_reader.IFileReader) *LaborManager {
    fileMonitorDuration, _ := time.ParseDuration("10s")
    return &LaborManager{
        mLaborConfs: map[string]*LaborConf{},
        mFileReader: fileReader,
        mTjApi:      tjApi,
        mLabors:     map[string]ILabor{},
        // 如果初始化成一個不帶快取的bool chan,會出現Shutdown方法的向channel設定true的操作會出現hang的情況
        //mQuit:       make(chan bool),
        // 這裡初始化一個帶1個數據的快取的bool channel來避免在相同的程序內部操作chan出現hang
        mQuit:       make(chan bool, 1),
        mTicker:     time.NewTicker(fileMonitorDuration),
        mLogger:     tj.GetLogger("labor_manager"),
    }
}

func (o *LaborManager) Shutdown() {
    for _, labor := range o.mLabors {
        labor.Shutdown()
        o.mLogger.Infof("labor done")
    }

    /*
    當bool channel初始化的時候就是帶快取的,直接向chan中設定並不會hang
    */
    o.mQuit <- true
    o.mLogger.Infof("quit is setted to true")
    /*
       如果bool channel為無快取channel,使用這段程式碼可是避免hang住
       go func() {
           o.mQuit <- true
           o.mLogger.Infof("quit is setted to true")
       }()
    */
}


func (o *LaborManager) runConfigChangedMonitor() {
    for {
        select {
        case <-o.mQuit:
            o.mLogger.Infof("config monitor has been exited")
            o.mLogger.Flush()
            o.mTicker.Stop()
            return
        case <-o.mTicker.C:
            o.mLogger.Infof("config monitor begin...")
            if o.IsConfigChanged() {
                o.mLogger.Infof("config changed, exiting...")
                o.Shutdown()
            }
        }
    }
}

程式輸出結果:


INFO labor_manager 2018/11/14 16:04:13.796465 labor_manager.go:171: config monitor begin...
INFO labor_manager 2018/11/14 16:04:23.796459 labor_manager.go:171: config monitor begin...
INFO labor_manager 2018/11/14 16:04:33.796429 labor_manager.go:171: config monitor begin...
INFO labor_manager 2018/11/14 16:04:43.796482 labor_manager.go:171: config monitor begin...
INFO labor_manager 2018/11/14 16:04:43.796621 labor_manager.go:173: config changed, exiting...
INFO labor_manager 2018/11/14 16:04:43.796638 labor_manager.go:99: labor done
INFO labor_manager 2018/11/14 16:04:43.796656 labor_manager.go:99: labor done
INFO labor_manager 2018/11/14 16:04:43.796660 labor_manager.go:103: quit is setted to true
INFO labor_manager 2018/11/14 16:04:43.796666 labor_manager.go:166: config monitor has been exited