Ключевое слово go немедленно возвращает выполнение из вызова функции, в то время как функция начинает работать в фоновом режиме в виде горутины, тогда как остальная часть программы тоже продолжает свое выполнение. Вы не можете контролировать или делать какие-либо предположения о порядке, в котором будут выполняться ваши программы, поскольку это зависит от планировщика ОС, планировщика Go и загрузки ОС.
Файловый сервер сам по себе не является веб-сервером, но тесно связан с веб-сервисами, поскольку реализуется с использованием аналогичных Go-пакетов. Кроме того, файловые серверы часто используются для поддержки функциональности веб-серверов и веб-сервисов.
Go предлагает для этого обработчик http.FileServer(), а также http.ServeFile(). Самая большая разница между ними заключается в том, что http.FileServer() — это http.Handler, тогда как в случае http.ServeFile() это не так. Кроме того, http.ServeFile() лучше обслуживает отдельные файлы, а http.FileServer() — целые деревья каталогов.
Сводка — похожа на гистограмму, но также может вычислять квантили по скользящим окнам, работающие со временем.
• Гистограмма — используется для выборки наблюдений‚ подсчета и сегментации. Гистограммы обычно служат для подсчета длительности запроса, времени отклика и т.д.
• Шкала — это единственное числовое значение, которое разрешено увеличивать или уменьшать. Шкалы обычно используются для представления значений, которые могут увеличиваться или уменьшаться, таких как количество запросов, продолжительность времени и т.д.
• Счетчик — это накопительное значение, которое используется для представления увеличивающихся счетчиков. Значение счетчика может оставаться неизменным, увеличиваться или сбрасываться до нуля, но не может уменьшаться. Счетчики обычно служат для представления совокупных значений, таких как количество запросов, обработанных на данный момент, общее количество ошибок и т.д.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type atomCounter struct {
val int64
}
Это структура для хранения требуемой атомарной переменной int64.
func (c *atomCounter) Value() int64 {
return atomic.LoadInt64(&c.val)
}
Это вспомогательная функция, которая возвращает текущее значение атомарной переменной int64, используя atomic.LoadInt64().
func main() {
X := 100
Y := 4
var waitGroup sync.WaitGroup
counter := atomCounter{}
for i := 0; i < X; i++ {
Мы создаем множество горутин, которые изменяют общую переменную. Как указывалось ранее, благодаря использованию пакета atomic для работы с общей переменной мы получаем простой способ избежать состояний гонки при изменении ее значения.
waitGroup.Add(1)
go func(no int) {
defer waitGroup.Done()
for i := 0; i < Y; i++ {
atomic.AddInt64(&counter.val, 1)
}
Функция atomic.AddInt64() безопасно изменяет значение поля val структуры counter.
}(i)
}
waitGroup.Wait()
fmt.Println(counter.Value())
}
При выполнении atomic.go в ходе проверки состояний гонки мы получаем такой вывод:
$ go run -race atomic.go
400
Таким образом, атомарная переменная изменяется несколькими горутинами без каких-либо проблем.
В следующем подразделе показано, как с помощью горутин совместно использовать память.
Атомарная операция — это операция, которая выполняется за один шаг относительно других потоков или, в данном случае, других горутин. Это означает, что атомарную операцию нельзя прервать в середине ее работы. Стандартная библиотека Go содержит пакет atomic, который в некоторых простых случаях может помочь избежать использования мьютекса. С помощью этого пакета вы получаете доступ к атомарным счетчикам из нескольких горутин, не имея проблем с синхронизацией и не беспокоясь о состояниях гонки. Однако мьютексы более универсальны, чем атомарные операции.
Тип sync.RWMutex
Тип данных sync.RWMutex является улучшенной версией sync.Mutex и определяется в файле rwmutex.go каталога sync стандартной библиотеки Go следующим образом:
type RWMutex struct {
w Mutex
writerSem uint32
readerSem uint32
readerCount int32
readerWait int32
}
Другими словами, sync.RWMutex основан на sync.Mutex и имеет необходимые дополнения и улучшения. Вы можете спросить, а как sync.RWMutex улучшает sync.Mutex? Хотя одной функции разрешено выполнять операции записи с мьютексом sync.RWMutex, у вас может быть несколько считывателей, владеющих мьютексом sync.RWMutex. Это означает, что операции чтения с помощью sync.RWMutex обычно выполняются быстрее. Тем не менее есть одна важная деталь, о которой следует знать: пока все считыватели мьютекса sync.RWMutex не разблокируют его, вы не сможете заблокировать его для записи. Такова небольшая цена, которую нам придется платить за повышение производительности‚ получаемое за счет возможности иметь несколько считывателей.
аждый вызов sync.Add() увеличивает счетчик в поле state1, которое представляет собой массив с тремя элементами uint32. Обратите внимание, что действительно важно вызвать sync.Add() перед операцией go, чтобы предотвратить какие-либо состояния гонки. О них вы узнаете в разделе «Состояния гонки» данной главы. Когда горутина завершает свою работу, должна быть выполнена функция sync.Done(), уменьшающая тот же счетчик на единицу. «За кулисами» sync.Done() просто совершает вызов Add(-1). Метод Wait() ожидает, пока этот счетчик не станет равным 0, после чего возвращается. Возврат Wait() внутри функции main() означает, что main() собирается вернуться и программа завершится.