Голанг: почему o.Exit не работает внутри горутин

У меня есть исследовательская программа с очень простым алгоритмом. Когда успех наступает, программа должна быть закрыта (завершена) через os.Exit(0). Я жду один день, два дня.... Что?:)

Вот простой код

package main

import "os"

func main() {
    for {
        go func() { os.Exit(0) }()
    }
}

И мои вопросы:

  1. Почему os.Exit не завершает программу?
  2. Как правильно прекратить (остановить) выполнение рутин?

Детская площадка: http://play.golang.org/p/GAeOI-1Ksc

3 ответа

Вы столкнулись с липким углом планировщика Go. Ответ в том, что os.Exit действительно вызывает завершение всего процесса, но так, как вы это сделали, горутины никогда не запускались.

Вероятно, произошло то, что цикл for продолжал добавлять новые подпрограммы в список доступных подпрограмм, но, поскольку весь процесс выполнялся только в одном потоке ОС, планировщик Go никогда не удосужился фактически запланировать другую подпрограмму, а просто продолжал ее выполнять. за цикл без запуска каких-либо горутин, которые вы породили. Попробуйте это вместо этого:

package main

import "os"

func main() {
    for {
        go func() { os.Exit(0) }()
        func() {}()
    }
}

Если вы запускаете его на Go Playground, он должен работать (на самом деле, вот ссылка).

Хорошо, тот факт, что приведенный выше код работает, а ваш - нет, должен быть довольно загадочным. Причина этого заключается в том, что планировщик Go на самом деле не выгружается. Это означает, что если определенная программа не решит добровольно дать планировщику возможность запускать что-то еще, ничего больше не будет работать.

Теперь очевидно, что вы никогда не писали код, включающий команды, чтобы дать планировщику возможность запуска. Что происходит, когда ваш код компилируется, компилятор Go автоматически вставляет их в ваш код. И вот ключ к тому, почему работает приведенный выше код: один из случаев, когда программа может решить запустить планировщик, - это когда вызывается функция. Таким образом, добавив func() {}() вызов (который, очевидно, ничего не делает), мы позволили компилятору добавить вызов в планировщик, давая этому коду возможность планировать различные программы. Таким образом, одна из порожденных гурутин запускает вызовы os.Exit и процесс завершается.

РЕДАКТИРОВАТЬ: сам вызов функции может быть недостаточно в случае, если компилятор встраивает вызов (или, в этом случае, удаляет его полностью, так как он ничего не делает). runtime.Gosched() С другой стороны, гарантированно работать.

Вы завершаете программу, возвращаясь из функции. Если вам нужно убедиться, что горуотин проходит до конца, вам нужно дождаться его завершения. Обычно это делается с sync.WaitGroupили синхронизируя горутины по каналам.

В вашем примере вам сначала нужно убедиться, что нет возможности порождать бесконечное количество горутин. Потому что между точками синхронизации нет main и новые программы, нет никакой гарантии, что кто-либо из них выполнит os.Exit вызов во время работы основного цикла.

Обычный способ ожидания выполнения любого количества процедур - использовать sync.WaitGroup, который будет гарантировать, что они все казнили раньше main выходы.

wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
    wg.Add(1)
    go func() { defer wg.Done() }()
}

wg.Wait()
fmt.Println("done")

Реализовать блокировку или убить переключатель

package main

import (
        "fmt"
        "time"
        "os"
)

const maxNoTickle = 50          // will bail out after this many no tickles
const maxWorking = 20           // pretendWork() will tickle this many times
const deadTicks = 250           // milliseconds for deadHand() to check for tickles
const reportTickles = 4         // consecutive tickles or no tickles to print something

var (
        tickleMe bool           // tell deadHand() we're still alive
        countNoTickle int       // consecutive no tickles
        countGotTickle int      // consecutive tickles
)

/**
*       deadHand() - callback to kill program if nobody checks in after some period
*/
func deadHand() {
        if !tickleMe {
                countNoTickle++
                countGotTickle = 0
                if countNoTickle > maxNoTickle {
                        fmt.Println("No tickle max time reached. Bailing out!")
                        // panic("No real panic. Just checking stack")
                        os.Exit(0)
                }
                if countNoTickle % reportTickles == 0 {
                        // print dot for consecutive no tickles
                        fmt.Printf(".")
                }
        } else {
                countNoTickle = 0
                countGotTickle++
                tickleMe = false        // FIXME: might have race condition here
                if countGotTickle % reportTickles == 0 {
                        // print tilda for consecutive tickles
                        fmt.Printf("~")
                }
        }
        // call ourselves again
        time.AfterFunc(deadTicks * time.Millisecond, deadHand)
}

/**
*       init() - required to start deadHand
*/
func init() {
        time.AfterFunc(250 * time.Millisecond, deadHand)
        tickleMe = true
}

/**
*       pretendWork() - your stuff that does its thing
*/
func pretendWork() {
        for count := 0; count < maxWorking; count++ {
                tickleMe = true // FIXME: might have race condition here
                // print W pretending to be busy
                fmt.Printf("W")
                time.Sleep(100 * time.Millisecond)
        }
}

func main() {
        go workTillDone()
        for {
                // oops, program went loop-d-loopy
        }
}
Другие вопросы по тегам