Потoкобезопасное завершение программы

Всем привет

Есть программа которая пишет в канал сообщение, читает это сообщение из канала, ожидает от пользователя SIGINT, заверщается если получает его и продолжает выполнение если не получает. В задании есть пункт "Выбрать и обосновать способ завершения работы воркеров".

Вопрос:

Какие способы зваершения работы воркеров вообще могут быть? Я ипользую общее завершение работы программы с помощью os.Exit(code) можно ли этот exit чем то потокобезопасным заменть и ресурсы вернуть или он уже потокобезопасный, можно ли завершать воркеры отдельно от основной программы, и как? Спасибо за внимание

Вот сама программа, только go playground почему то завершает ее сразу и цикл игнорит, поэтому лучше на машине запускать


Ответы (2 шт):

Автор решения: Eugene X

Ты-же сам используешь os.Exit, это по факту и есть аварийное завершение программы. Для того что-бы завершать программу безопасно создай второй канал, и проверяй в рутинах оба канала (твой и терминатор). Если канал завершения программы отработал завершаешь рутины, и завершаешь программу в обычном режиме.

Альтернативный способ, это закрытие рабочего канала через close(channel) и обработка этого события.

package main
import (
    "fmt"
    "time"
)
func main() {
    var message = make(chan string) // Создаём канал, что-бы складывать в него строки
    go func() {
        for i := 1; i <10; i++ {
            time.Sleep(time.Second)
            message <- fmt.Sprintf("Chan value %d", i) // Асинхрорнно пишем в канал
        }
        close(message) // Закрывем канал, в который больше нечего не запишится
    }()
    for {
        text, ok := <- message // Ждём пока в канале что-то появиться или канал закроется
        if !ok {
            break
        }
        println(text)
    }
}

https://eugene.stormway.ru/works/programmer/go/goroutine-and-channels

→ Ссылка
Автор решения: Maksim Fedorov

Можете использовать контекст снаружи от создания горутин и передавать им его. В горутине в select слушайте ctx.Done() и если пришло сообщение в него раньше, чем горутина закончила работу — делайте return

Как-то так (если ctx оповестил раньше, то горутина завершится), в данном случае горутина слушает др канал (в некоторых случаях необходимо еще в цикл завернуть) https://go.dev/play/p/HeBD8QSdPhU

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

const ProgramStoppedByUser = 100

func main() {
    ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)

    var amountOfWorkers int
    flag.IntVar(&amountOfWorkers, "a", 2, "-a <amount of workers>")
    flag.Parse()

    times := make(chan int64, 1)

    // Запускаем ваши воркеры
    WriteInChannel(times, amountOfWorkers, ctx)

    for {
        select {
        // Главная программа завершится: закроет канал и вернет код ответа
        case <-ctx.Done():
            close(times)
            log.Println("Stopped by user")
            os.Exit(ProgramStoppedByUser)
        default:
            // Если нет завершения — плодим время, можно это вынести в горутины 
            // (но не забудьте ее тоже через контекст закрыть) и убрать текущий цикл for
            // Также закрытие канала в пишущую горутину поместить
            now := time.Now().UnixNano()
            times <- now
            time.Sleep(1 * time.Second)
        }
    }
}

func WriteInChannel(times chan int64, amountWorkers int, ctx context.Context) {
    for i := 0; i < amountWorkers; i++ {
        go Worker(i, times, ctx)
    }
}

func Worker(id int, ch <-chan int64, ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d is closed \n", id)
            return
        case data, ok := <-ch:
            if !ok {
                fmt.Print("Chan already closed \n")
                return
            }
            fmt.Printf("Data: %d (Worker %d) \n", data, id)
        }
    }
}
→ Ссылка