Оказывается, выходная поза сервера тоже может быть такой элегантной

задняя часть Go
Оказывается, выходная поза сервера тоже может быть такой элегантной

Эта статья приняла участие"Проект "Звезда раскопок"", Выигрышное веселье создание, вызов создание поощрительных выплат.

Я считаю, что xdm, написавший golang, написал и http-сервер, круто ли использовать golang для написания сервера?

Самый простой http сервер

Напишем простой http-сервер

func main() {
	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	http.ListenAndServe(":9090", srvMux)

}

func getinfo(w http.ResponseWriter, r *http.Request) {
	fmt.Println("i am xiaomotong!!!")
	w.Write([]byte("you are access right!!\n"))
}

Эта функция очень проста, заключается в мониторинге локального9090порт, и один из URL-адресов будет обрабатывать запрос,/getinfo, мы можем запросить увидеть эффект с помощью следующей команды

# curl localhost:9090/getinfo
you are access right!!

Понятно, что к нему можно нормально обращаться, и он тоже получит нашу соответствующую информацию, и лог сервера тоже в норме.

Давайте подумаем, если в это время происходит авария, программа падает, паникует или что-то, что мы думаем, убито, как мы судим, как сервер выходит?

добавить сигнальный сервер

Когда мы пишем на C/C++, мы должны быть знакомы с сигналами, верно?golangВнутри мы также добавляем сигнал, чтобы определить, является ли онkillпроцедурный

В линуксе можно пройтиman kill Подробное описание Kill Directive

Здесь мы можем увидетьkill -9 соответствуетSIGKILLsignal , мы также знаем, что сигнал SIGINTCtrl-CЭтот сигнал будет выдаваться, когда непонятно, и это тоже сигнал прерывания.Если вы в этом не уверены, то можете поискать в интернете.список сигналов Linux

func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(":9090", srvMux)

	 fmt.Println(<-sig)
}

http.ListenAndServeзаблокирован, то здесь мы слушаем9090Сервис открыт для отдельного процесса

проверять

# go run main.go
^Cinterrupt

В это время наш http-сервер смог различить сигнал и узнать, как выйти

Наши потребности постепенно возрастают, в реальной работе мы не должны быть в состоянии делать это так

выйти изящно

На работе у нас есть http сервер, и там должна быть другая логика обработки, такая как чтение и запись файлов, связь GRPC или использование базы данных, то если наша программа закрыта, нам все равно придется с ней разбираться по ситуации .атомарность

Возможны следующие две ситуации:

  • Там нет строгих требований к качеству для данных, и не имеет значения, если программа панична, так что нет проблем без изящного отключения в это время.
  • Для тех, кто может работать с базой данных, читать и записывать файлы и т. д., будет изменять данные, упомянутые выше, не ожидается, что процесс работы с данными будет прерван.Мы должны следовать атомарности, наша программа должна предоставить буферизованное время для корректного выхода.

Выход должен быть изящным при нормальной работе

Как добиться изящного выхода?

Например, в приведенном выше примере, когда основной COROUTINE принимает сигнал прерывания, он немедленно выйдет из программы, а под-Coroutine также выйдет соответственно

Если основная сопрограмма должна дождаться, пока подпрограмма обработает текущую работу, прежде чем выйти, то нужно ли нам позволить основной сопрограмме и подпрограмме взаимодействовать друг с другом, чтобы это стало возможным?

Используйте 2 канала для плавного завершения работы

Этот метод легче представить

Реализация условно делится на2 шагаидти:

  • После того, как основная сопрограмма получает сигнал прерывания, она уведомляет подпрограмму о корректном закрытии, которая называется здесьstopCh
  • После того, как подпрограмма получает уведомление, обработав уведомление под рукой, основная сопрограмма закрывает программу, которая названа здесьcloseCh
func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan int)
	closeCh := make(chan int)

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(":19999", srvMux)

	go func(stopCh, closeCh chan int) {
		for {
			select {
			case <-stopCh:
				fmt.Println(" processing tasks")
                // 模拟正在处理数据
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				closeCh <- 1
			}
		}
	}(stopCh, closeCh)

	<-sig
	stopCh <- 1
	<-closeCh
	fmt.Println("close server ")
}

Здесь мы видим, что 2 канала используются для того, чтобы основная сопрограмма и подпрограмма могли общаться друг с другом.

Откройте сопрограмму и выполните анонимную функцию для мониторингаstopChЕсли у канала есть данные, если есть данные, это означает, что основная сопрограмма получила сигнал и уведомила под-сопрограмму о корректном закрытии.

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

Реализовано с использованием вложенных каналов

Использование вложенных каналов для корректного закрытия может показаться неожиданным, но официальный сайт дает нам некоторые указания.

Идея реализации такова:

  • Используйте канал stopCh, элемент в канале stopCh является другим каналом tmpCh
  • Когда основная сопрограмма получает сигнал выхода, она записывает данные tmpCh в stopCh и начинает отслеживать tmpCh на наличие данных.
  • Когда подпрограмма считывает данные tmpCh из stopCh, она знает, что ее нужно корректно закрыть.После обработки собственных дел подпрограмма записывает данные в tmpCh.
  • Когда основная сопрограмма прослушивает данные в tmpCh, она выходит из программы.
func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan chan struct{})

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(":19999", srvMux)

	go func(stopCh chan chan struct{}) {
		for {
			select {
			case tmpCh:=<-stopCh:
				fmt.Println(" processing tasks")
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				tmpCh <- struct{}{}
			}
		}
	}(stopCh)

	tmpCh := make(chan struct{})

	<-sig
	stopCh <- tmpCh
	<-tmpCh
	fmt.Println("close server ")
}

Вышеупомянутые два метода похожи. Оба они используют каналы для достижения плавного завершения работы. Каналы — это врожденные структуры данных golang, и нам нужно их использовать.

Использовать стандартный контекст решения golang

Использование контекста golang может лучше решить проблему корректного завершения работы.

Не думайте, что контекст используется только для передачи данных, контекст также может управлять жизненным циклом подпрограмм.

func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan struct{})
    // 创建一个上下文
	ctx,cancle := context.WithCancel(context.Background())

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(":19999", srvMux)

	go func(ctx context.Context,stopCh chan struct{}) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println(" processing tasks")
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				stopCh <- struct{}{}
			}
		}
	}(ctx,stopCh)

	<-sig
	cancle()
	<-stopCh
	fmt.Println("close server ")
}

Здесь мы используем метод контекста. Когда основная сопрограмма закрывает контекст, под-сопрограмма считывает данные из канала, а затем изящно закрывает его. Мы можем видеть исходный код,ctx.Done()Возвращаемое значение также является каналом

Основная сопрограмма ожидает, пока все подпрограммы корректно закроют метод реализации.

Когда дело доходит до вышесказанного ждем когда основная корутина 1 подкорутина шикарно закроется, закрываем сами программы

Тогда в реальной работе должно быть более одной сопрограммы,То, что мы хотим сделать, это элегантность, затем элегантность в конце, вот наш подходРеализуется контекстом + sync.WaitGroup в golang

func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)
	ctx, cancle := context.WithCancel(context.Background())

	mywg := sync.WaitGroup{}
	// 控制 5 个子协程的声明周期
	mywg.Add(5)

	for i := 0; i < 5; i++ {
		go func(ctx context.Context) {
			defer mywg.Done()
			for {
				select {
				case <-ctx.Done():
					fmt.Println(" processing tasks")
					time.Sleep(time.Second * time.Duration(1))
					fmt.Println("process over ")
					return

				default:
					time.Sleep(time.Second * time.Duration(1))
				}
			}
		}(ctx)
	}

	<-sig
	cancle()
	// 等待所有的子协程都优雅关闭
	mywg.Wait()
	fmt.Println("close server ")
}

В приведенном выше коде мы используем sync.WaitGroup для управления жизненным циклом 5 подпрограмм.Когда основная сопрограмма получает сигнал прерывания,cancle()Drop CTX.

Каждый ребенок может быть изctx.Done()После прочтения данных и обработки вещей на руках самостоятельно

наконецdefer mywg.Done(), основная сопрограммаmywg.Wait()Дождавшись корректного закрытия всех сопрограмм, я также закрываю свою собственную программу.

Проверьте эффект

# go run main.go
^C processing tasks
processing tasks
processing tasks
processing tasks
processing tasks
process over
process over
process over
process over
process over
close server

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

Добро пожаловать лайк, подписка, избранное

Друзья, ваша поддержка и поощрение мотивируют меня продолжать делиться и улучшать качество.

Хорошо, вот и на этот раз

Технологии открыты, и наш менталитет должен быть открытым. Примите перемены, живите на солнце и двигайтесь вперед.

ямаленький дьяволенок Нежа, добро пожаловать, лайкайте, подписывайтесь и добавляйте в избранное, увидимся в следующий раз~