Потoкобезопасное завершение программы
Всем привет
Есть программа которая пишет в канал сообщение, читает это сообщение из канала, ожидает от пользователя SIGINT, заверщается если получает его и продолжает выполнение если не получает. В задании есть пункт "Выбрать и обосновать способ завершения работы воркеров".
Вопрос:
Какие способы зваершения работы воркеров вообще могут быть? Я ипользую общее завершение работы программы с помощью os.Exit(code) можно ли этот exit чем то потокобезопасным заменть и ресурсы вернуть или он уже потокобезопасный, можно ли завершать воркеры отдельно от основной программы, и как? Спасибо за внимание
Вот сама программа, только go playground почему то завершает ее сразу и цикл игнорит, поэтому лучше на машине запускать
Ответы (2 шт):
Ты-же сам используешь 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
Можете использовать контекст снаружи от создания горутин и передавать им его. В горутине в 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)
}
}
}