Як вийти з програми go на честь відкладених дзвінків?


76

Мені потрібно використовувати deferбезкоштовні розподіли, створені вручну за допомогою Cбібліотеки, але os.Exitв якийсь момент мені також потрібно зі статусом не 0. Хитра частина полягає в тому, що os.Exitпропускає будь-яку відкладену інструкцію:

package main

import "fmt"
import "os"

func main() {

    // `defer`s will _not_ be run when using `os.Exit`, so
    // this `fmt.Println` will never be called.
    defer fmt.Println("!")
    // sometimes ones might use defer to do critical operations
    // like close a database, remove a lock or free memory

    // Exit with status code.
    os.Exit(3)
}

Дитячий майданчик: http://play.golang.org/p/CDiAh9SXRM, викрадене з https://gobyexample.com/exit

Тож як вийти із програми go, що вшановує заявлені deferдзвінки? Чи існує якась альтернатива os.Exit?



@ctcherry не має успіху з deferos.Exit : play.golang.org/p/IsSI9VB7j8
marcio

змінити порядок відстрочки play.golang.org/p/a4RP5BiXbc
ctcherry

о, я розумію, це проблема. У моєму випадку я можу відстрочити лише os.Exitпісля запуску інших операцій, які теж щось відкладають ... дозвольте мені трохи подумати.
marcio

deferв mainпоєднанні з os.Exit, безумовно, грубе місце. Архітектура навколо нього, як це робить @Rob Napier, є кращим способом.
ctcherry

Відповіді:


28

runtime.Goexit() це найпростіший спосіб досягти цього.

Goexit припиняє програму, яка його викликає. Жоден інший горутин не впливає. Goexit запускає всі відкладені дзвінки перед тим, як завершити роботу програми. Оскільки Goexit не панікує, проте будь-які виклики відновлення в цих відкладених функціях повернуть нуль.

Однак:

Виклик Goexit з головної програми припиняє цю програму без повернення функції main. Оскільки func main не повернувся, програма продовжує виконувати інші програми. Якщо всі інші програми виходять, програма аварійно завершує роботу.

Отже, якщо ви називаєте це з основного горутину, у верхній частині mainпотрібно додати

defer os.Exit(0)

Нижче, можливо, ви захочете додати деякі інші deferзаяви, які повідомляють інші програми про зупинку та очищення.


1
Я й гадки не мав, що runtime.Goexit()існує. Це з недавнього випуску?
марсіо

2
@marcio Я копав у сховищах Go. Я не міг точно дізнатися, коли він був представлений, але я знайшов цей тест, який посилається на нього та є "Авторським правом 2013", до того, як ви опублікували це питання.
ЕМБЛЕМА

2
@marcio Я ще трохи копав і знайшов архів документації 2009 року . Goexit вказаний там.
ЕМБЛЕМА

Я позначу це як прийняту відповідь. Інші відповіді, безумовно, все ще діють, але це, здається, найпростіший підхід - поки що: D
marcio

43

Просто перемістіть програму вниз за рівнем і поверніть код виходу:

package main

import "fmt"
import "os"

func doTheStuff() int {
    defer fmt.Println("!")

    return 3
}

func main() {
    os.Exit(doTheStuff())
}

Тож я не повинен використовувати deferвсередині func mainабо будь-яку функцію, яка виходить?
marcio

4
До речі, я не рекомендую використовувати os.Exit()випадкові місця в коді. Це дуже ускладнює тестування, крім проблеми кодів помилок. Рішення peterSO про те, що посилання @ctcherry є нормальним, але воно погано масштабує IMO до більшої програми. Вам доведеться зробити codeглобальний. Я вважаю, що вам слід тримати main () досить простим, і нехай він просто дбає про речі на рівні ОС (наприклад, про остаточний код стану).
Роб Нейпір,

Привіт, я знайшов спосіб впоратися з цим, не нав'язуючи жодної архітектури. У будь-якому випадку, +1, тому що це гарна порада завжди спочатку спробувати KISS.
marcio

28

Після деяких досліджень, зверніться до цього , я знайшов альтернативу:

Ми можемо скористатися panicі recover. Виявляється, що panic, за своєю природою, буде виконувати deferвиклики, але також завжди виходитиме з 0кодом не стану і скидатиме трасування стека. Фокус у тому, що ми можемо замінити останній аспект панічної поведінки за допомогою:

package main

import "fmt"
import "os"

type Exit struct{ Code int }

// exit code handler
func handleExit() {
    if e := recover(); e != nil {
        if exit, ok := e.(Exit); ok == true {
            os.Exit(exit.Code)
        }
        panic(e) // not an Exit, bubble up
    }
}

Тепер, щоб вийти з програми в будь-який момент і зберегти будь-яку заявлену deferінструкцію, нам просто потрібно видати Exitтип:

func main() {
    defer handleExit() // plug the exit handler
    defer fmt.Println("cleaning...")
    panic(Exit{3}) // 3 is the exit code
}

Це не вимагає ніякого рефакторингу, крім підключення рядка всередині func main:

func main() {
    defer handleExit()
    // ready to go
}

Це досить добре масштабується з більшими базами коду, тому я залишу його доступним для вивчення. Сподіваюся, це допоможе.

Дитячий майданчик: http://play.golang.org/p/4tyWwhcX0-


Найкраща відповідь з усіх поки що! Одне питання, як тоді боротися з нормальним виходом? Як переконатися, що вам handleExitзателефонують навіть при звичайному виході? Посилання: play.golang.org/p/QDJum4kOXk скасуйте коментарі //panicта перегляньте різницю.
xpt

Я думаю, що дескриптор для звичайної події виходу 0 не існує, але ви завжди можете впасти в паніку (Вихід {0}), а потім обробити це або, можливо, відкласти handleNormalExit () перед чим-небудь ще на main ()?
марсіо

16

Для нащадків це було більш елегантне рішення:

func main() { 
    retcode := 0
    defer func() { os.Exit(retcode) }()
    defer defer1()
    defer defer2()

    [...]

    if err != nil {
        retcode = 1
        return
    }
}

1
Це справді бере найкращі частини інших відповідей.
PRMan

-1

Я б запропонував використовувати горутин

func main(){
     defer fmt.Println("Hello world")
     go func(){
          os.Exit(0)
     }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.