В последнее время Сяобай изучает голанг, записывая некоторые заметки в процессе обучения и собственное понимание.
- Реализация сопрограмм в go
- синхронизировать блокировку синхронизации для сопрограмм в ходу
- канал в ходу
- диапазон в движении
- выберите переключить сопрограмму в ходу
- Кэшированный канал в ходу
- Планирование корутин в ходу
Оригинальный адрес:GitHub.com/fort и все…
приветственная звезда
Прежде чем вводить сопрограмму в go, сначала посмотрите на функцию отсрочки в следующем ходу, функция отсрочки не является обычной функцией, функция отсрочки будет выполняться после возврата обычной функции. Функция defer может освобождать внутренние переменные функции, закрывать соединение с базой данных и т. д., например:
func print(){
fmt.Println(2);
}
func main() {
defer print();
fmt.Println(1);
}
В приведенном выше примере сначала выводится 1, а затем 2, что указывает на то, что defer действительно выполняется после окончания обычного вызова функции.
Go использует сопрограммы для работы с параллелизмом.Сопрограммы можно понимать как меньшие потоки, занимающие меньше места и требующие меньшего переключения контекста потока.
Можно подробно описать преимущества следующих сопрограмм: сопрограммы легче, чем потоки, они могут быть созданы с использованием памяти стека 4 КБ и могут обрабатывать большое количество задач с небольшим объемом памяти.
В го Ctrip вызывается через ключевое слово go, по ключевому слову видно, что очень важная фишка голанга — это сопрограммы.
1. Реализация сопрограмм в go
Давайте посмотрим на пример:
func printOne(){
fmt.Println(1);
}
func printTwo(){
fmt.Println(2);
}
func printThree(){
fmt.Println(3);
}
func main() {
go printOne();
go printTwo();
go printThree();
}
Выполняя вышеуказанную основную функцию, мы обнаружили, что нет вывода 123, как мы думали, потому что, хотя сопрограмма является параллельной, если функция, которая вызывает сопрограмму, завершается до вызова сопрограммы, сопрограмма умирает с кончиной сопрограммы. программа.
Поэтому мы можем приостановить работу основной функции в основной функции и увеличить количество событий, ожидающих вызова сопрограммы.
func main() {
go printOne();
go printTwo();
go printThree();
time.Sleep(5 * 1e9);
}
Таким образом, будет вызвана соответствующая функция сопрограммы, модифицированная ключевым словом go. Давайте посмотрим на результаты выполнения 3 раза отдельно.
- первый раз 1 3 2
- второй раз 3 2 1
- в третий раз 3 1 2
Мы обнаружили, что, поскольку сопрограмма выполняется параллельно, мы не можем определить порядок ее вызовов, поэтому результат каждого вызова основной функции не определен.
Из приведенных выше примеров сопрограмм мы видим, что при использовании сопрограмм необходимо учитывать две проблемы:
- Как контролировать порядок вызова сопрограмм, особенно когда разные сопрограммы одновременно обращаются к одному и тому же ресурсу.
- Как реализовать связь между разными сопрограммами
Вопрос 1 может быть достигнут с помощью блокировки синхронизации, вопрос 2, канал предоставляется для реализации связи между различными сопрограммами.
2. Блокировка синхронизации синхронизации сопрограммы в ходу
Пакет синхронизации в go предоставляет две блокировки: мьютекс sync.Mutex и блокировку чтения-записи sync.RWMutex.Мы используем мьютекс, чтобы решить вышеуказанную проблему, заключающуюся в том, что разные сопрограммы могут планировать один и тот же ресурс в одно и то же время, и переписать вышеприведенное пример:
func printOne(m *sync.Mutex){
m.Lock();
... do something use DB or other source
defer m.Unlock();
}
func printTwo(m *sync.Mutex){
m.Lock();
... the same thing as printOne do something use DB or other source
defer m.Unlock();
}
func main() {
m:= new(sync.Mutex);
go printOne(m);
go printTwo(m);
time.Sleep(5 * 1e9);
}
При взаимном исключении printOne и printTwo не конкурируют за один и тот же ресурс.
3. Канал канала в ходу
Существует специальный тип канала, который может отправлять типизированные данные через канал для реализации связи между сопрограммами, а метод связи через канал также обеспечивает синхронизацию.
Способ объявления канала очень прост:
var ch1 chan string
ch1 = make(chan string)
Мы используем ch для представления канала, и обозначение канала включает поток в канал (отправка): ch
При этом go также поддерживает объявление односторонних каналов:
var ch1 chan int //普通的channel
var ch2 chan <- int //只用于写int数据
var ch3 <- chan int //只用于读int数据
Приведенные выше определения относятся ко всем каналам без буферов или каналам с длиной 1. Характеристики этого канала:
После того, как данные помещены в канал, данные должны быть удалены, прежде чем можно будет вставить другие данные.Это синхронный канал.Отправитель и получатель канала одновременно передают только одну часть данных, а затем должны ждать другую сторону, чтобы выполнить соответствующие действия отправки и получения.
Мы по-прежнему используем приведенный выше пример вывода 123 и используем канал синхронизации для реализации вывода синхронизации.
func printOne(cs chan int){
fmt.Println(1);
cs <- 1
}
func printTwo(cs chan int){
<-cs
fmt.Println(2);
defer close(cs);
}
func main() {
cs := make(chan int);
go printOne(cs);
go printTwo(cs);
time.Sleep(5 * 1e9);
}
В приведенном выше примере 12 будут выводиться по очереди, так что мы можем добиться синхронного вывода за счет синхронизации каналов.
Мы упоминали ранее, что для того, чтобы дождаться завершения выполнения сопрограммы go, мы используем time.sleep в основной функции для приостановки основной функции, по сути, сама основная функция также может рассматриваться как сопрограмма. канал, вам не нужно использовать его в основной функции. Используйте time.sleep для приостановки.
Перепишем приведенный выше пример:
func printOne(cs chan int){
fmt.Println(1);
cs <- 1
}
func main() {
cs := make(chan int);
go printOne(cs);
<-cs;
close(cs);
}
В приведенном выше примере будет выведено 1. Мы не приостановили его по time.sleep в основной функции, а заменили его каналом, ожидающим записи.
Примечание. Каналы можно закрывать явно, когда вам нужно сообщить получателю, что начальное значение не будет предоставлять новых значений, вам нужно закрыть канал.
4. Дальность действия
Мы также говорили о своевременном закрытии канала, но неэффективно постоянно обращаться к источнику данных и проверять, не закрылся ли канал. Ключевое слово range предоставляется в файле go.
Когда ключевое слово диапазона используется в канале, оно автоматически будет ожидать действия канала, пока канал не будет закрыт. Популярным моментом будет то, что канал может автоматически переключаться.
То же, например:
func input(cs chan int,count int){
for i:=1;i<=count;i++ {
cs <- i
}
}
func output(cs chan int){
for s:= range cs {
fmt.Println(s);
}
}
func main() {
cs := make(chan int);
go input(cs,5);
go output(cs);
time.Sleep(3*1e9)
}
В приведенном выше примере последовательно выводятся 1, 2, 3, 4, 5. При использовании ключевого слова range, когда канал закрывается, цикл for приемника автоматически останавливается.
5. Выберите переключение сопрограммы в ходу
Извлечение значений из разных параллельных исполнений может быть выполнено с помощью ключевого слова select, которое очень похоже на оператор управления переключением, также известный как переключатель связи.
Прежде всего, что делает select? ?
В select есть механизм опроса, Select отслеживает данные, поступающие в канал, или когда канал отправляет значение, операция в кейсе выполняется после отслеживания соответствующего поведения.
Объявление выбора:
select {
case u:= <- ch1:
...
case v:= <- ch2;
...
}
Точно так же давайте рассмотрим пример использования select:
func channel1(cs chan int,count int){
for i:=1;i<=count;i++ {
cs <- i
}
}
func channel2(cs chan int,count int){
for i:=1;i<=count;i++ {
cs <- i
}
}
func selectTest(cs1 ,cs2 chan int){
for i:=1;i<10;i++ {
select {
case u:=<-cs1:
fmt.Println(u);
case v:=<-cs2:
fmt.Println(v);
}
}
}
func main() {
cs1 := make(chan int);
cs2 := make(chan int);
go channel1(cs1,5);
go channel2(cs2,3);
go selectTest(cs1,cs2);
time.Sleep(3*1e9)
}
输出结果为:1,2,1,2,3,3,4,5 总共8个数据。且因为没有做同步控制,因此运行几次后的输出结果是不相同的。
6. Канал с кешем в ходу
Упомянутые выше — это все каналы без буферов или каналы с длиной 1. На самом деле, каналы тоже могут быть буферизованы, длину канала мы можем выполнить при объявлении.
ch = make(chan string,3)
Например, в приведенном выше примере длина канала ch указана равной 3, а канал, длина которого не равна 1, можно назвать каналом с буферизацией.
Буферизованный канал может непрерывно записываться до тех пор, пока его длина не будет заполнена.
ch <- 1
ch <- 2
ch <- 3
7. Планирование корутин в ходу
Когда дело доходит до параллелизма, необходимо упомянуть о планировании сопрограмм в go. Пакет времени выполнения в go предоставляет функцию планировщика. Пакет среды выполнения предоставляет следующие методы:
- Gosched: позволить текущему потоку отказаться от процессора, чтобы позволить другим потокам работать, он не приостановит текущий поток, поэтому текущий поток продолжит выполняться в будущем.
- NumCPU: возвращает количество ядер ЦП в текущей системе.
- GOMAXPROCS: установите максимальное количество ядер ЦП, которые можно использовать одновременно.
- Goexit: выход из текущей горутины (но оператор отсрочки будет выполняться как обычно)
- NumGoroutine: возвращает общее количество задач, выполняемых и поставленных в очередь.
- GOOS: целевая операционная система
Для машин с многоядерными процессорами go может явно указать компилятору расписание запуска сопрограмм go на нескольких процессорах.
import "runtime"
...
cpuNum:=runtime.NumCPU;
runtime.GOMAXPROCS(cpuNum)
Давайте поговорим о принципе планирования в GO, сначала определимся с концепцией следующей модели:
M: количество потоков в ядре G: сопрограмма в go, наименьшая единица параллелизма, созданная ключевым словом go в go. P: Процессор, контекст сопрограммы G, каждый P будет поддерживать локальную очередь сопрограммы.
Далее давайте посмотрим на классическую диаграмму, объясняющую планирование сопрограмм в GO:
Поясним изображение выше:
- P — это количество процессоров, мы часто устанавливаем GOMAXPROCS планировщика на количество процессоров, поэтому здесь P — это обычно количество процессоров машины.
- M — это поток, поток связан с процессором P, а набор пар P и M образует локальную очередь сопрограмм.
- G — это сопрограмма, которую необходимо добавить в локальную очередь, состоящую из P и M, для обработки по очереди.
- В дополнение к локальным сопрограммам, очередь сопрограмм также поддерживается глобально.
- Если все очереди обрабатываются в локальной очереди сопрограмм и новой очереди нет, то М-поток отменит занятие ЦП, и М-поток уйдет в сон