Если вы уже наигрались с Go, устали от копипасты, ручного жонглирования мьютексами и всерьёз подумываете о приобретении протезов для рук, то позвольте предложить вашему вниманию перевод Tour of the Go с эквивалентным кодом на D и краткими пояснениями.
Часть первая. Основы.
Часть пятая. Сопрограммы.
Coroutines
Go
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Go позволяет легко и просто запустить любую функцию параллельным потоком и продолжить работу, не дожидаясь её завершения. Все гопотоки (волокна, fibers, сопрограммы, coroutines, greenlets) выполяются кооперативно на ограниченном числе нативных потоков (нитях, threads), тем самым максимально утилизируя ядра процессора (cores). Стандартная библиотека D поддерживает волокна, но лишь в рамках одной нити и не умеет балансировать волокна на несколько нитей. Но такой планировщик реализован в проекте vibe.d, правда синтаксис запуска параллельного потока всё ещё не столь лаконичен как в Go. Поэтому мы воспользуемся библиотекой go.d предоставляющией шаблон "go!" для параллельного запуска функций. Кроме того, следуя лучшим практикам, код примеров мы будем оформлять в виде тестов.
D
unittest
{
import core.time;
import std.range;
import jin.go;
__gshared static string[] log;
static void saying( string message )
{
foreach( _ ; 3.iota ) {
sleep( 100.msecs );
log ~= message;
}
}
go!saying( "hello" );
sleep( 50.msecs );
saying( "world" );
log.assertEq([ "hello" , "world" , "hello" , "world" , "hello" , "world" ]);
}
В D не принято велосипедить без надобности, поэтому цикл мы реализовали через итерирование по последовательности натуральных чисел. Функцию "saying" нам пришлось объявить статической, чтобы она не имела доступа к локальным переменным, что небезопасно при параллельном выполнении её в разных нитях. Если сделать эту функцию замыканием, убрав "static", то данный код не скомпилируется — благодаря шаблонной магии компилятор не разрешит нам направлять пистолет в собственные конечности. В Go же вопрос конкурентного доступа остаётся на совести программиста, у которого, в большинстве случаев, её нет.
Buffered Channels
Go
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Запускать параллельные потоки было бы не так полезно, если бы не было возможности их синхронизации. Go использует довольно элегантную абстракцию для этого — каналы. Каналы представляют из себя типизированные очереди сообщений. Если поток пытается прочитать что-то из пустого канала, то он блокируется в ожидании другого потока, которые эти данные туда запишет. И наоборот, если попытается записать в переполненный канал, то заблокируется, пока другой поток не вычитает из канала хотя бы одно сообщение. Каналы легко и просто заменяют такие абстракции как ленивые генераторы, события и обещания, привнося с собой гораздо больше сценариев использования.
В стандартной библиотеке D для общения между потоками используется приём/передача абстрактных сообщений. То есть, зная id потока вы можете послать ему произвольное сообщение, а он должен его распаковать и как-то обработать. Довольно не удобный механизм. Vibe.d вводит абстракцию потока байт с поведением, аналогичным гоканалам. Но зачастую требуется не просто байты передавать, а некоторые структуры. Кроме того, что в Go, что в D, межпотоковая коммуникация реализована через захват мьютекса, что имеет небезызвестные проблемы. Поэтому мы опять воспользуемся библиотекой go.d, предоставляющей нам типизированные wait-free каналы.
D
unittest
{
import jin.go;
auto numbers = new Channel!int(2);
numbers.next = 1;
numbers.next = 2;
numbers.next.assertEq( 1 );
numbers.next.assertEq( 2 );
}
Виртуальное свойство "next", конечно, не так наглядно, как стрелочка в Go, зато компилятор пристально следит за положением нашего пистолета, и не позволяет передать через канал типы, не безопасные для параллельного использования из разных нитей. Однако, есть один момент — эти каналы требуют чтобы у них было не больше одного читателя и не больше одного писателя. К сожалению, пока за этим приходится следить вручную, но в будущем наверняка и тут компилятор перейдёт в наши союзники.
Также стоит отметить, что размер канала в Go по умолчанию равен одному элементу, а в go.d около 512 байт.
Channels
Go
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y) // -5 17 12
}
В Go работа с каналом защищена мьютексом, так что вы можете использовать его для коммуникации сразу с несколькими потоками, когда вам не важно, в каком порядке они предоставят данные. Каналы из библиотеки go.d, напротив, неблокирующие, поэтому в таком сценарии их использовать нельзя — для каждого потока необходимо создать свой коммуникационный канал. Для упрощения работы со списками каналов библиотека предоставляет структуры-балансировщики Inputs и Outputs. В данном случае нам потребуется Inputs, которая по очереди читает из каждого непустого канала, зарегистрированного в ней.
D
unittest
{
import std.algorithm;
import std.range;
import jin.go;
static auto summing( Channel!int sums , const int[] numbers ) {
sums.next = numbers.sum;
}
immutable int[] numbers = [ 7 , 2 , 8 , -9 , 4 , 0 ];
Inputs!int sums;
go!summing( sums.make(1) , numbers[ 0 .. $/2 ] );
go!summing( sums.make(1) , numbers[ $/2 .. $ ] );
auto res = sums.take(2).array;
( res ~ res.sum ).assertEq([ 17 , -5 , 12 ]);
}
Как обычно, мы не пишем руками суммирование диапазона, а используем для этого стандартный обобщённый алгоритм "sum". Чтобы подобные алгоритмы работали с вашим типом данных достаточно реализовать один из интерфейсов диапазонов, которые, разумеется, реализованы как в Channel, так и в Inputs, и в Outputs. Алгоритм "take" выдаёт ленивый диапазон, возвращающий указанное число первых элементов исходного диапазона. А алгоритм "array" выгребает из диапазона все элементы и возвращает нативный массив с ними. Обратите внимание, что каждому потоку мы передаём отдельный канал единичной длинны и срез неизменяемого массива (привет, параллелизм!).
Range and Close
Go
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
Как видно, в Go мы тоже можем итерироваться по каналу, последовательно получая из него очередные элементы. Чтобы не зависнуть в бесконечном цикле, такие каналы должны закрываться передающей стороной, чтобы принимающая могла понять, что больше данных не будет и цикл пора заканчивать. В D мы бы написали практически то же самое, разве что объявили бы ряд Фибоначчи в виде математической рекурентной формулы.
D
unittest
{
import std.range;
import jin.go;
static auto fibonacci( Channel!int numbers , int count )
{
auto range = recurrence!q{ a[n-1] + a[n-2] }( 0 , 1 ).take( count );
foreach( x ; range ) numbers.next = x;
numbers.close();
}
auto numbers = new Channel!int(10);
go!fibonacci( numbers , numbers.size );
numbers.array.assertEq([ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 ]);
}
Но можно ещё сильнее упростить код, зная, что шаблон "go!" сам умеет перекладывать значения из диапазона в канал.
D
unittest
{
import std.range;
import jin.go;
static auto fibonacci( int limit )
{
return recurrence!q{ a[n-1] + a[n-2] }( 0 , 1 ).take( limit );
}
fibonacci( 10 ).array.assertEq([ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 ]);
go!fibonacci( 10 ).array.assertEq([ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 ]);
}
Таким образом, функции вовсе не обязательно знать ничего про каналы, чтобы иметь возможность запускать её параллельным потоком, а потом дожидаться от неё результата.
Select
Go
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Go имеет специальный лаконичный синтаксис для одновременной работы с несколькими каналами. D ничего такого, разумеется, не имеет. Однако, эквивалентный функционал реализуется не особо сложнее ручной реализацией цикла наблюдения.
D
unittest
{
import std.range;
import jin.go;
__gshared int[] log;
static auto fibonacci( Channel!int numbers , Channel!bool control )
{
auto range = recurrence!q{ a[n-1] + a[n-2] }( 0 , 1 );
while( !control.closed )
{
if( numbers.needed ) numbers.next = range.next;
yield;
}
log ~= -1;
numbers.close();
}
static void print( Channel!bool control , Channel!int numbers )
{
foreach( i ; 10.iota ) log ~= numbers.next;
control.close();
}
auto numbers = new Channel!int(1);
auto control = new Channel!bool(1);
go!print( control , numbers );
go!fibonacci( numbers , control );
while( !control.empty || !numbers.empty ) yield;
log.assertEq([ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , -1 ]);
}
Как видно, нам пришлось избавиться от замыкания, а в циклах добавить "yield", чтобы конкурирующие волокна тоже могли что-то сделать, пока текущее висит в ожидании.
Default Selection
Go
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
Специальный синтаксис в Go позволяет сделать что-то, если ни в одном из каналов не было активности. В D у вас, тем не менее, больше контроля над потоком исполнения.
D
unittest
{
import core.time;
import jin.go;
static auto after( Channel!bool channel , Duration dur )
{
sleep( dur );
if( !channel.closed ) channel.next = true;
}
static auto tick( Channel!bool channel , Duration dur )
{
while( !channel.closed ) after( channel , dur );
}
auto ticks = go!tick( 101.msecs );
auto booms = go!after( 501.msecs );
string log;
while( booms.clear )
{
while( !ticks.clear ) {
log ~= "tick";
ticks.popFront;
}
log ~= ".";
sleep( 51.msecs );
}
log ~= "BOOM!";
log.assertEq( "..tick..tick..tick..tick..BOOM!" );
}
Примечательной особенностью является то, что нам не потребовалось вручную создавать канал. Если функция первым аргументом принимает канал и мы его не передали, то он будет создан автоматически и возвращён как результат работы шаблона "go!", что весьма удобно. Функции "after" и "tick" слишком специфические, чтобы вносить их в общую библиотеку, но реализации у них весьма простые.
Mutex
В некоторых случаях без разделяемого изменяемого состояния всё же не обойтись и тут нам на помощь приходят блокировки.
Go
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mux.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
Да, именно так, реализация разделяемого изменяемого состояния в Go — это боль и страдания. Одно неверное движение при работе со мьютексами и вы внезапно обнаружите у себя фантомные конечности. Не говоря уж о том, что компилятор даже не намекнёт вам о том, где мьютексы необходимы. А вот компилятор D вас сильно наругает за попытку работы с незащищённым изменяемым состоянием из разных потоков. А самый простой способ защитить состояние при многопоточном выполнении — реализовать синхронизированный класс.
D
unittest
{
import core.atomic;
import core.time;
import std.range;
import std.typecons;
import jin.go;
synchronized class SafeCounter
{
private int[string] store;
void inc( string key )
{
++ store[key];
}
auto opIndex( string key )
{
return store[ key ];
}
void opIndexUnary( string op = "++" )( string key )
{
this.inc( key );
}
}
static counter = new shared SafeCounter;
static void working( int i )
{
++ counter["somekey"];
}
foreach( i ; 1000.iota ) {
go!working( i );
}
sleep( 1.seconds );
counter["somekey"].assertEq( 1000 );
}
Особенность синхронизированного класса в том, что для него автоматически создаётся мьютекс и при вызове любого публичного метода этот мьютекс захватывается, освобождаясь лишь при выходе из метода. При этом всё внутреннее состояние обязано быть приватным. Но тут есть одна неприятная особенность (а на самом деле очень опасный и досадный баг компилятора): шаблонные методы, такие как, например, "opIndexUnary!", не заворачиваются в захват мьютекса. Поэтому мы создали отдельный публичный метод "inc", который и вызываем из шаблонного метода. Внутренняя реализация получилась уже не столь красивой, зато внешний интерфейс получился как родной. Полученный "shared SafeCounter" мы уже можем спокойно передавать через канал и использовать напрямую из разных потоков.
62% (45) |
Управление потоком исполнения |
37% (27) |
Составные типы |
26% (19) |
Методы |
Проголосовало 73 человека. Воздержалось 43 человека.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (89)
Dicebot
29.03.2016 10:28Но такой планировщик реализован в проекте vibe.d
Формулировка немного вводит в заблуждение — планировщик vibe.d не перемещает task/fiber между потоками. После того, как worker thread был выбран, конкретный task будет выполняться именно в нём до самого завершения. Причём это не дефект реализации, а осознанное решение, т.к. перемещение между потоками очень недружелюбно к оптимизации и кэшированию, а удобство, по большей части, мнимое.
JekaMas
29.03.2016 10:30Интересная статья!
Есть два вопроса:
есть ли в D эквивалент Go-шного race detector?
в приведенных примерах, как и сказано в статье, волокна D выполняются параллельно, в Go — конкурентно. То есть приведенные примеры не являются эквивалентными. Есть где примеры именно эквивалентных конкурентных программ Go и D.
Статья интересная, но странная. С самого начала заяввили, что вещи не эквивалентны и давай их сравнивать… Зачем? Они же о разном…vintage
29.03.2016 10:35+1Речь о такого рода сообщениях? "fatal error: all goroutines are asleep — deadlock!". Такого пока нет.
И в Go и в go.d/vide.d волокна выполняются конкурентно на фиксированном числе системных нитей. Так что примеры эквивалентны.JekaMas
29.03.2016 10:41Нет, я не только о выявлении во время выполнения, но и об https://golang.org/doc/articles/race_detector.html. Он и помогает разработчикам ничего себе не отстрелить.
Выше уже написали, что планировщики не эквивалентны друг другу.
Кстати, а в какой миг viber.d решает, что пора переключиться на другой fiber внутри системного потока?
Количество системных потоков можно задавать из программы?vintage
29.03.2016 10:50+1The race detector only finds races that happen at runtime, so it can't find races in code paths that are not executed. If your tests have incomplete coverage, you may find more races by running a binary built with -race under a realistic workload.
Так себе костыль. Статические проверки надёжнее.
Выше уже написали, что планировщики не эквивалентны друг другу.
Они не идентичны, но в данных примерах они действуют эквивалентно — раскидывают волокна по ограниченному набору нитей.
Кстати, а в какой миг viber.d решает, что пора переключиться на другой fiber внутри системного потока?
Когда вызывается функция yield, переключающая контексты.
Количество системных потоков можно задавать из программы?
Разумеется.JekaMas
29.03.2016 11:05Вы сужаете 'эквивалентность' тут. Гошка дает управление конкурирующими горутинами и позаботится о переключении выполняющейся сейчас задачи. Программист может это сделать и в ручном режиме, но нет надобности: я знаю, что при отправке значения в канал, io операции, системном вызове или GC, планировщик позаботится о том, какую горутину выполнять следующей.
Получается, что в D управление конкурентностью 'ручное' и несколько менее изящное. Первое должно увеличивать число ошибок программистов на D при написании конкурентностью о кода.
Возможный в целом подход, но на мой взгляд более рискованный и в коде не смотрится естественно.vintage
29.03.2016 11:53+2Любая кооперативная многозадачность "ручная", что в Go, что в D. В этом её единственное отличие от вытесняющей. Реализации блокирующих примитивов (каналы, мьютексы и тп) в обоих языках "вручную" вызывают yield для освобождения нити.
JekaMas
29.03.2016 13:09Однако не 'в ручную'. В Go у тебя есть планировщик и правила переключения и вытеснения.
Вопрос про количество ошибок и времени вхождения остается открытым.vintage
29.03.2016 13:26+1Нету в Go никакого выстеснения, о чём вы? Я ещё раз повторяю, степень ручной работы в обоих языках одна и та же. Посмотрите примеры в статье. yield в одном из примеров — эквивалент Gosched, который вам точно также придётся вызывать вручную, например, в таком случае:
for { select { case <-boom: fmt.Println("BOOM!") return default: fmt.Print("."); runtime.Gosched(); } }
JekaMas
29.03.2016 14:35Но в GO мне не нужно вызывать Goshed, я не думаю о том, что сейчас делает планировщик, в каком из тредов будет исполнена моя горутина, будет ли она "украдена" другим тредом или нет.
Я думаю о том, какая часть кода когда должна отрабатывать, оставляя реализацию "под капотом" GO. Это принципиальное отличие. (пример, как по-другому делается приведенный вами код — ниже в комментарии).vintage
29.03.2016 14:56+1При кооперативной многозадачности всегда надо думать о том сколько волокно непрерывно владеет нитью.
Так что если вы реализуете тяжёлое вычисление и не вставляете периодически Goched, то рано или поздно столкнётесь с такой проблемой, что все нити будут заняты вычислениями трёх волокон, а остальным придётся ждать своего часа продолжительное время..
Ваш код делает не то же самое, а реализует совсем другой алгоритм.
lega
31.03.2016 06:57На сколько я знаю Go может переключить на другую горутину в любой момент (если горутина работает долго и не прерывается для yield), тут плюс в том что не нужно самому вставлять yield в долгие обработки, и минус — в некоторых местах нужно использовать локи с расчетом что горутина может переключится в любой момент, а в DLang наоборот (в этом плане DLang мне больше нравится).
Ещё одна разница, горутины сами разливаются по потокам (и ядрам) в отличие от файберов в DLang. И тут тоже плюсы и минусы, плюс в том что можно нагрузить все ядра, минус — коммуникация между горутинами из разных потоков убивает производительность.
Т.е. в Dlang можно лучше затюнить, но больше кода. С другой стороны горутины в Go переключаются быстрее чем файберы в Dlang, в Go на это упор.vintage
31.03.2016 08:53-1На сколько я знаю Go может переключить на другую горутину в любой момент
А тут говорят обратное.
Ещё одна разница, горутины сами разливаются по потокам (и ядрам) в отличие от файберов в DLang.
Задачи в vibe.d работают аналогично.
Dlang можно лучше затюнить, но больше кода.
Во всех примерах эквивалентного кода на D получилось меньше.cy-ernado
31.03.2016 12:09А тут говорят обратное.
Дата публикации статьи — 11 апреля 2012.
Задачи в vibe.d работают аналогично.
Огласите список популярных проектов на vibe.d, желательно с публикациями. Хотя бы уровня того самого криковатого фронтенда.vintage
31.03.2016 12:27Дата публикации статьи — 11 апреля 2012
Приведите статью по свежее, пожалуйста :-)
Огласите список популярных проектов на vibe.d, желательно с публикациями. Хотя бы уровня того самого криковатого фронтенда.
Опять писькомерство. Ну, вот, например: www.sociomantic.comcy-ernado
31.03.2016 13:01Ну, вот, например: www.sociomantic.com
А можно opensource и чтобы было видно, кто этим пользуется (из компаний)?
Просто на Go популярные есть проекты с открытым исходным кодом, которые я могу сейчас взять и потрогать. Это Docker, InfluxDB, вот уже и CockroachDB. Да и тот же Gogs.
Кстати, а на D есть распределенные базы данных? :)
Согласен с тем, что в этом есть доля писькомерства, но все таки большое количество разнообразных популярных проектов для языка, который появился не так давно — показатель его практичности, дело явно не в легаси.
Я вижу, например, что на го даже довольно неожиданные проекты пишут.
Приведите статью по свежее, пожалуйста :-)
Я поискал, ситуация действительно изменилась с тех пор, но немного. Как раз тогда Вьюков переписал шедулер.
Вот ссылки:
https://github.com/golang/go/issues/11462
https://github.com/golang/go/issues/10958
http://morsmachine.dk/go-scheduler
Если кратко, то в го планировщик не полностью вытесняющий.Dicebot
31.03.2016 14:33+2Согласен с тем, что в этом есть доля писькомерства, но все таки большое количество разнообразных популярных проектов для языка, который появился не так давно — показатель его практичности
Это очень популярное заблуждение. Практичность и вообще технологические достоинства языка имеют сравнительно малое влияние на популярность (если только он не откровенно ужасен). Решает громкое имя, агрессивный маркетинг, удачные пилотные проекты и в целом snowball effect. Комментарии в этом треде ("мало библиотек, должно быть с языком что-то не так") это только лишний раз подтверждают.
Программисты тоже люди :)
vintage
31.03.2016 14:38Go распиарен куда сильнее, с этим никто не спорит. Но как и любой хайп, этот вскоре закончится.
Социомантик как раз и запилил свою распределённую базу. Но как и любой коммерческий проект не особо горит желанием её оупенсорсить. А вот кто финансирует разработку всяких Gogs и CockroachDB? Очевидно, корпорация добра не плохо вложилась в продвижение языка. С тем же успехом она могла бы популяризировать любой примитивный язык. Basic, например, или JavaScript. Ой, вы только посмотрите сколько успешных проектов на JS — вот она, серебряная пуля, не эти ваши никому не нужные нишевые компилируемые языки.
Судя по ссылкам он вообще не вытесняющий. Пока горутина не разрешит — никто её с нити не снимет.cy-ernado
31.03.2016 15:06А вот кто финансирует разработку всяких Gogs и CockroachDB?
У CockroachDB 20 fulltime разработчиков и список инвесторов открыт, а в Gogs большую часть написал один разработчик Unknwon, можно у него спросить, сколько ему гугл платит.
Очевидно, корпорация добра не плохо вложилась в продвижение языка. С тем же успехом она могла бы популяризировать любой примитивный язык. Basic, например, или JavaScript.
Не очевидно. С Dart так не получилось, хотя его, в отличие от Go, усиленно пиарили.
сколько успешных проектов на JS
Legacy, отсутствие альтернатив. Плохой пример. Посмотрим, что будет, если WebAssembly выстрелит.
Социомантик как раз и запилил свою распределённую базу.
Если учитывать еще и полностью коммерческие проекты на Go, не выложенные в опенсорс, то D вообще видно не будет. Надо спросить у itoolabs, Badoo, Yandex, Uber, Dropbox, Twitch, Cloudflare, Microsoft, New Yourk Times, DigitalOcean, Disqus, Iron.io, MongoDB, SoundCloud, SpaceX (список получается уже слишком большим, аналогичного о D нет), сколько им гугл занес денег. Многие из этих компаний начали использовать Go до хайпа, когда сообщество было мало, историй успеха почти не было, а версия 1.0 только появлялась. Гугл делал язык для себя, а пиар гуглом сильно преувеличен. Кстати, можете сами посчитать упоминание о Google на https://golang.org/.
lega
01.04.2016 09:18Пока горутина не разрешит — никто её с нити не снимет.
Хз, я поверял так: делал лимит на 1 поток, и запускал не прерывный бесконечный цикл, и при этом соседние горутины отрабатывали.vintage
01.04.2016 11:30+1Пример кода приведёте?
cy-ernado
01.04.2016 15:00При вызове функции в цикле с определенной вероятностью горутина может отпустить поток.
Если просто суммировать число в бесконечном цикле, то не отпустит точно, но такие случаи очень редки и видимо пошли на компромисс. Issue на гитхабе открыт и когда это станет критично, то исправят.
Dicebot
31.03.2016 14:26Ну, вот, например: www.sociomantic.com
Мы используем не vibe.d, а схожую по архитектуре, но самописную систему.vintage
31.03.2016 14:40Не суть важно в данном контексте :-) Может расскажите свою историю (не)успеха? Go судя по всему выехал в основном на них.
Dicebot
02.04.2016 12:43Многовато придётся рассказывать :) Don довольно много упоминает в http://dconf.org/2014/talks/clugston.html
Что-то конкретное интересует?vintage
02.04.2016 14:26Всё интересует, может серию статей?
Dicebot
02.04.2016 15:53Всё интересует, может серию статей?
Очень неохота, такие статьи надо утверждать в отделе маркетинга и вообще возни много. Лучше подождите месяц и послушайте http://dconf.org/2016/talks/lucarella.html и http://dconf.org/2016/talks/panel2.html
Dicebot
29.03.2016 11:09волокна D выполняются параллельно, в Go — конкурентно
Это неверное утверждение. В обоих языках происходит параллельное выполнение worker threads внутри которых происходит конкурентное выполнение fibers. Волокна вообще не могу выполняться параллельно сами по себе, по их определению. Разница на таком низком уровне только в том, что планировщик Go может переместить уже выполняемый fiber в другой worker thread (что создаёт иллюзию конкурентности и между goroutine), а в vibe.d соответствие неизменно.
beduin01
29.03.2016 10:58А кто может объяснить как файберы работают? Как я понимаю они просто не все регистры процессоры загружают и из-за этого переключение контекста быстрее идет?
Что будет если файбер повис. Поток тоже повиснет?
Как вообще очередь устроена. Как программа понимает какие данные для какого файбера предназначены? И ведь нужно еще время на то чтобы нужный файбер активировать.Dicebot
29.03.2016 11:29https://en.wikipedia.org/wiki/Cooperative_multitasking
Реализация на практике чаще всего сводится к выделению памяти для стека и регистров и коду, который переключает регистры на другой контектст. Существенное отличие от threads в том, что операционная система про них ничего не знает и контекст переключается только когда где-то в коде программы явно вызывается функция видаFiber.yield()
.
Эффективность тут даже не столько в скорости переключения контекста (хотя экономить на syscalls всегда важно), сколько в количестве переключений. Равномерное распределение процессорного времени на тысячи потоков может стать причиной того, что программа проводит все время в переключениях и не выполняет полезной работы. В случае с yield, контектст будет переключен только когда программа сама посчитает это нужным.
Что будет если файбер повис. Поток тоже повиснет?
Да, поэтому системы основанные на fiber практически всегда используются в сочетании с async I/O
Как вообще очередь устроена
По разному. Концепция fiber это базовый системный примитив, который умеет только переключать контексты и больше ничего. Различные библиотеки могут реализовывать разную логику очередей на основе этого примитива. В случае с D, Fiber является частью стандартной библиотеки. А вот конкретная очередь, о которой идёт речь в статье, уже реализована в сторонней библиотеке vibe.d и основана на event loop (например, libevent).
Обычно такая библиотека также определяет абстракцию Task (например http://vibed.org/api/vibe.core.task/Task) которая использует Fiber для реализации, но также определяет такие вещи как модель передачи данных между контекстами. В некотором роде goroutine — это task в стандартном go runtime scheduler.beduin01
29.03.2016 12:32Что можно почитать по основам этого всего?
Dicebot
31.03.2016 14:36Я для этого читал исходники :) Хотя наверняка должна быть литература по старым операционными системам (до появления многозадачности), которые использовали как раз cooperative multitasking. Fiber это всего лишь аналогичная концепция применённая в рамках одного процесса.
creker
29.03.2016 12:06+2Но такой планировщик реализован в проекте vibe.d, правда синтаксис запуска параллельного потока всё ещё не столь лаконичен как в Go
Совсем не такой, от слова совсем. Для начала надо реализовать такую мелочь как динамический расширяемый стек, иначе весь смысл файберов вообще теряется. Ну и заканчивая тем, что планировщик намного сложнее и не требует ручной передачи управления, потому что все делается само с помощью рантайма и компилятора. Этим D всегда будет отличаться и не сможет догнать банально потому что Go содержит все это в своей спецификации. В особенности это касается сетевого I/O, которое, по сути, с планировщиком одно целое.
Однако, эквивалентный функционал реализуется не особо сложнее ручной реализацией цикла наблюдения.
Не реализуется. Select может ждать на любом количестве каналов, не будет жрать процессор или требовать использовать костыли в виде Sleep или условных переменных. Все потому что рантайм и планировщик все сделают сами. В том числе дадут другим горутинам работать, пока кто-то ждет на select. В этой конструкции вся суть модели конкурентности Go и простым циклом она не реализуется при всем желании.
На подумать — конкурентность Go ни капли не страдает даже в условиях, когда всего один worker thread. Более того, до 1.5 именно так и было — по-умолчанию количество потоков было равно 1. В 1.5 его стали задавать равным числу процессорных ядер.vintage
29.03.2016 13:09-3Совсем не такой, от слова совсем.
Не вырывайте цитаты из контекста: "Все гопотоки (волокна, fibers, сопрограммы, coroutines, greenlets) выполяются кооперативно на ограниченном числе нативных потоков (нитях, threads), тем самым максимально утилизируя ядра процессора (cores). "… "такой планировщик реализован в проекте vibe.d".
Для начала надо реализовать такую мелочь как динамический расширяемый стек, иначе весь смысл файберов вообще теряется.
Копировать стек при переполнении — не самая светлая идея на мой взгляд. А позволять смертельно зациклившейся в рекурсии программе отъедать до 250 мегабайт памяти под стек — вообще какая-то дичь.
В особенности это касается сетевого I/O, которое, по сути, с планировщиком одно целое.
vibe.d использует асинхронный I/O. Но стандартная библиотека, да, использует синхронный.
Select может ждать на любом количестве каналов, не будет жрать процессор или требовать использовать костыли в виде Sleep или условных переменных.
Да нет, ожидание нескольких каналов в Go реализуется так же бесконечным циклом: play.golang.org/p/fodHZpVaci
На подумать — конкурентность Go ни капли не страдает даже в условиях, когда всего один worker thread.
В D она тоже не страдает :-)JekaMas
29.03.2016 14:28+2Пример не совсем корректный. Вот так было бы лучше https://play.golang.org/p/rX7SbuOAKP
Одна горутина, один канал, один таймер. Select не нужно делать в бесконечном цикле.
Вы привели пример не ожидания нескольких каналов, в вычисления в бесконечном цикле, внутри которого есть каналы. Вычисление с циклом выносится в отдельную горутину и остается чистый select, который умеет ждать любое количество каналов.vintage
29.03.2016 14:40-1Мой пример показывает, что горутина будет крутиться в бесконечном цикле пожирая процессор, пока не будет остановлена либо явно через Gosched, либо неявно, через Sleep или обращение к пустому/полному каналу.
Ваш же пример просто переносит этот блокирующий цикл в другую горутину, но он также не отпустит нить, пока не завершится.JekaMas
29.03.2016 14:45+2Изначальное утверждение "Select может ждать на любом количестве каналов, не будет жрать процессор или требовать использовать костыли в виде Sleep или условных переменных."
Select НЕ ЖРЕТ процессор. Собственно это и показано в более корректном примере. Select отдельно, вычисления отдельно.
Ваш пример был или "не о том" или неверен, в том смысле, что он не опровергает утверждения о том, что select способен ждать любого количества каналов, не выжирая процессор и без sleep.
bfDeveloper
29.03.2016 14:08jin.go — отличная библиотека, но всё же select не получился. Приведённый в статье пример очень так себе. Гораздо лучше с задачей селекта справляются обычные D сообщения. С сообщениями можно писать так:
receive( (int val) { }, (double val) { }, (int i, double d) { }, (MyStruct s) { } );
Тогда из другой задачи можно писать
tid.send(42); tid.send(3.14); tid.send(10, 10.0); tid.send(MyStruct(args));
Основной недостаток в том, что сообщение отправляется задаче, а не в некий канал. Передача данных реализуется через передачу владения, что быстро и безопасно. Просто не выглядит как поток или канал.vintage
29.03.2016 14:30Изначально была аналогичная реализация, но с замыканиями не очень удобно работать. Например, чтобы выйти из цикла, приходится писать так:
auto quit = false; for( !quit ){ receive( (int val) { }, (Quit q) { quit = true; } ); }
Кроме того, селект по типу, вынуждает вводить кучу лишних типов исключительно для этого селекта. Так что ручной цикл по каналам получается в итоге проще, понятней и даёт больше гибкости.
IvanPanfilov
29.03.2016 15:04-4> Если вы уже наигрались с Go, устали от копипасты, ручного жонглирования мьютексами и всерьёз подумываете о приобретении протезов для рук, то позвольте предложить вашему вниманию перевод Tour of the Go с эквивалентным кодом на D и краткими пояснениями.
ну а где эквивалетная стандартная библиотека с хотябы с таким же составом и сабильностью библиотек golang.org/pkg и поддерживаемая разработчиками?vintage
29.03.2016 15:08+4IvanPanfilov
29.03.2016 16:35-4и где там библиотека для http сервера и клиента и шаблонов?
где библиотеки для работы с изображениями и криптографией?vintage
29.03.2016 16:54+4IvanPanfilov
29.03.2016 17:43-4я спрашивал про стандартную библиотеку от производителя а не поделки с интернета
все ясно окончательно с эти вашим D.YourLastDoctor
29.03.2016 20:41+3я спрашивал про стандартную библиотеку от производителя а не поделки с интернета
все ясно окончательно с эти вашим C.
я спрашивал про стандартную библиотеку от производителя а не поделки с интернета
все ясно окончательно с эти вашим C++.
я спрашивал про стандартную библиотеку от производителя а не поделки с интернета
все ясно окончательно с эти вашим JS.IvanPanfilov
30.03.2016 07:19-8тут речь про современный язык для прикладного по и сетевых утилит
где в статье было упоминание про переход с GO на С++ или С?
и причем сдесь JS? вы идиот?
замечу спаециаелно для ретардов — сдесь конкретное сравнение Go vs D — перечитайте статью еще раз
IvanPanfilov
29.03.2016 16:40-7работа с почтой? поддержка rpc? тоже нет?
вывод — D сферическиский язычек в вакууме — без батареек, но зато якобы красивый.vintage
29.03.2016 16:58+3cy-ernado
31.03.2016 22:00-1Полнофункциональный драйвер для mongodb на D есть?
Который хоть кто-нибудь использует и написал об этом.vintage
31.03.2016 23:30+1cy-ernado
01.04.2016 14:13-1Gridfs, automated cluster topology discovery and maintenance, failover, pre-fetching, гибкая настройка consistency-level?
Хорошо ли протестирована библиотека? Кто ей пользуется?
Authenticated connections are supported by using a URL connection string such as «mongodb://user:password@host». Note that the driver currently only supports the «MongoDB-CR» authentication mechanism. Since new MongoDB versions, starting with 3.0, default to the new «SCRAM-SHA-1» method, it is necessary to manually switch to the old method. See
stackoverflow.com/questions/29006887/mongodb-cr-authentication-failed for more information.
Я бы не называл это полнофункциональным. Условие "Который хоть кто-нибудь использует и написал об этом." вроде бы тоже не выполняется.
А значит, нет :)
Зато на го есть. И его используют сами 10gen и это один из самых лучших драйверов для монги. Я считаю, что при разработке приложения, которое использует эту БД, логичнее выбирать Go, чем D. Как вы считаете?vintage
01.04.2016 16:53Я считаю, что монгу вообще не стоит использовать, но это уже совсем другая история :-)
GeckoGreen
29.03.2016 16:02То есть Select реализуется простым перебором? Получается, что мы крутим вечный цикл с проверкой не пришло ли нам чего-нибудь?
Как-то много смен контекста может выйти и очень сильно выжирает проц, думаю. В Go же запустить рутину или нет решает планировщик. Что весьма удобно. Выходит, что корутины на D потяжелее горутин.vintage
29.03.2016 16:08А как по вашему планировщик это решает? ;-)
Переключение волокна — это просто смена указателя на стек. Но погонять бенчмарки, конечно, надо. :-)GeckoGreen
29.03.2016 16:17Как я понял, в D происходит запуск рутины, проверка наличия данных в канале и смена контекста, если нет. В Go рутина при чтении из канала и в случае отсутствия данных в нем добавляется в очередь на чтение из него. И потом, при появлении в канале данных, рутина отправляется в очередь на запуск. Ну и так далее. Select же добавляет в несколько очередей на чтение из каналов. И при отсутствии секции default планировщик запусти рутину только в случае наличия данных в одном из каналов.
Kerbiter
29.03.2016 16:02Приятно видеть, что D на 3 месте. Я начал его изучать — как по мне, неплохой язык (неэкспертное мнение cppшника-самоучки).
GeckoGreen
29.03.2016 16:33К слову, на github 115 470 репозиториев на Go. На D же 6 985. При этом D более чем в два раза старше.
IvanPanfilov
29.03.2016 16:46-3потомучто просто языки нафиг не нужны никому кроме автора и кучки энтузиастов — какие бы они красивые не были.
нужна богатая инфраструктура и стандартная либа — примеры успеха Swift, Kotlin со всей java инфраструктурой и ее богатством ну и Go естественно.
vintage
29.03.2016 17:09+2GeckoGreen
29.03.2016 17:16-1Я не говорил, что это ключевой фактор. Но решив написать на D, я рискую не найти либы для какого-нибудь специфичного протокола, например Diameter. При этом не вижу серьезных причин, почему качество репозиториев на D должно быть лучше.
vintage
29.03.2016 18:13+2Но решив написать на D, я рискую не найти либы для какого-нибудь специфичного протокола
Вы всегда можете подключить существующую библиотеку на языках C, C++, Objective-C.
При этом не вижу серьезных причин, почему качество репозиториев на D должно быть лучше.
IvanPanfilov
29.03.2016 19:02> Вы всегда можете подключить существующую библиотеку на языках C, C++,
везде можно использовать C, C++,
а вот писать на одном языке позволяют только нормальные современные языки с богатой и продуманной библиотекой.
а весь красивый и уютный мирок D в вакууме рушится об реальный мир разработки ПО и вынужденное копание в стороннем коде.
— я использовал D для написания пары небольших утилит и GUI програм лет 5 назад — с тех пор никаких изменений сущесвенных в D так и не случилось. D не стал популярным.bfDeveloper
29.03.2016 20:59+2везде можно использовать C, C++,
Как много языков имеют ABI совместимость с C и C++, не требуют никаких прослоек, используют совместимые типы и не внедряются в систему сборки?
В каком языке вы можете просто слинковать существующую системную библиотеку и начать использовать, не подключая всяких биндингов? Мне, например, для использования простой библиотеки потребовалось всего-лишь описать её интерфейс в .di файле. Это заняло не больше, чем клон самой библиотеки с гитхаба. Для более сложных случаев есть специальная утилита htod. И нет, это не автогенератор прослоек или биндингов, это просто генератор заголовков для использования из D. Никаких накладных расходов, никаких преобразований типов.
Я видел, что предлагает Rust, знаю как пишутся плагины для node.js, активно использовал JNI, на работе пишу XS для perl, и поверьте, удобнее, чем из D c C и C++ взаимодействуют только С и С++.IvanPanfilov
30.03.2016 12:56-3вот именно сколько лет прошло — а кроме «удобного ABI» так ничего и не появилось
значит язык не так уж и хорош на деле если используют сишные libcurl odbc и прочее и не хотят делать свои имплементации на красивом DbfDeveloper
30.03.2016 14:11Для чего-то важного и популярного типа libcurl есть свои реализации. Тот же vibe.d. Там есть даже свой event loop, оборачивающий kqueue, epoll и win32 в общий интерфейс, называется libasync. А некоторые вещи лучше вообще не переписывать, например OpenGL. Его лучше максимально дёшево слинковать.
Ещё ABI очень важен для редких задач. Упомянутый мной выше co2mon из питона и node.js люди используют стартом отдельного процесса, с которого берут stdin и stdout. Это действительно наиболее простое решение для них. А D просто линкует библиотеку, зовёт функцию и получает данные.
D позволяет решать действительно сложные и редкие задачи просто, а Go простые задачи очень просто.
Здесь всё зависит от задач и от программистов. Те, кто писал сайтики на php могут продолжить заниматься этим на Go. Те кто закапывался в алгоритмы и оптимизации в C++ радуются D.
GeckoGreen
29.03.2016 22:09+1В Go есть встроенные решения для unit тестирования и бенчмарков. Не принято создавать пэкэдж для 1 функции.
Просто неясно, почему нужно перейти с Go на D. Корутины в D работают, судя по всему не так хорошо и просто как в go. Неизвестно насколько они дешевы. Могу ли я вместо использования паттерна Worker'ов использовать порождение корутин?
Кстати, есть ли в D какая-то замена кросс-компиляции? Вещь на самом деле удобная и нужная.
Есть ли опыт успешного применения в больших проектах в продакшене?
Да и вообще есть ли большие проекты на D?
Обобщенный код — это, конечно, удобно, но как показала практика не необходимо.vintage
29.03.2016 22:32-1Неизвестно насколько они дешевы.
Давайте потестим, выбирайте поле для битвы :-)
Могу ли я вместо использования паттерна Worker'ов использовать порождение корутин?
Не понял о чём вы.
Кстати, есть ли в D какая-то замена кросс-компиляции?
Не интересовался этим вопросом, но через LLVM должно быть можно.
Да и вообще есть ли большие проекты на D?
А есть ли большие проекты на Go?
Обобщенный код — это, конечно, удобно, но как показала практика не необходимо.
Все так говорят, пока проект не погряз в копипасте.JekaMas
29.03.2016 22:55+1"Все так говорят, пока проект не погряз в копипасте."
Можно примеры из мира разработки на GO?
GeckoGreen
29.03.2016 23:06+1А есть ли большие проекты на Go?
Ниже привели примеры. Если посетить сайты данных проектов, то можно увидеть, какие крупные компании ими пользуются.
Все так говорят, пока проект не погряз в копипасте.
Знаете, за чуть менее чем десяток немалых проектов на Go я копировал только один файл с функциями для логирования и делал я это только потому, что считаю это незаслуженным для отдельного пакета. Возможно, мне повезло.
Давайте потестим, выбирайте поле для битвы :-)
Думаю для проверки эффективности корутин/каналов следует написать что-то такое http://play.golang.org/p/XX9uhAPUB9
Не знаю насколько результаты будут отражать действительность.
cy-ernado
29.03.2016 18:11А сколько на D проектов уровня Docker, Kubernetes, Syncthing, etcd, InfluxDB, NSQ, Consul, Prometheus, Nomad?
Язык появился достаточно давно и если он во всем так прекрасен, то проектов должно быть много. Александреску даже в Facebook успел поработать (в котором, иронично, под проекты на go отдельное место на гитхабе выделили).
Пока что видно только vibe.d, который был уже тогда, когда только выходила первая версия го и мне рассказывали, насколько крутой D и как же я глупо поступаю, выбирая Go.
В последнее время появляются истории о переходе на go с других ЯП с одной стороны, и man!(GO => D) с другой. Кажется, я чего-то не понимаю, но первым доверяю больше, чем абстрактным словам о преимуществах.vintage
29.03.2016 22:23+3А какой там у них уровень? Я про них ничего не слышал. Кроме разве что докера, который — не более, чем кривоватый фронтенд к линуксовым контейнерам.
Качество языка никак не кореллирует с его популярностью, к сожалению. Вы и сами демонстрируете это — выбираете язык не по возможностям, не по выразительности, не по скорости, не багоёмкости, а по популярности.cy-ernado
29.03.2016 22:55У них много кода и и пользователей.
Go удобно и просто решает мои практические задачи, я на нем пишу на работе и для себя. Это получается быстро, особо с прекрасным плагином для IDEA, авторы которого (из Jetbrains) мне могут ответить в реальном времени в русском slack по го (где я в том числе нашёл место работы и где мне могут помочь с go).
Go использует огромное количество компаний, для части из них это основной язык разработки, что даёт некоторую уверенность в будущем языка. Мне регулярно (пусть и не часто) приходят на почту предложения поработать на go. Я могу послушать русский подкаст о этом языке каждую неделю, и даже поучаствовать в нем, если захочу, поделившись опытом. У меня есть много коллег, которые пишут на этом языке с опытом разработки реального проекта, который приносит деньги.
Что может предложить мне D из этого?
Googolplex
30.03.2016 12:08+3Docker — кривоватый фронтенд к линуксовым контейнерам? о_О да уж. Даже если вам чем-то не нравятся детали его реализации, вы не можете отрицать, что подход докера к поставке и развёртыванию приложений сильно упрощает эти процессы. Я помню, как лет десять назад на локальном форуме в файлообменной сети провайдера я писал инструкции по настройке Apache+PHP+MySQL, и как это было довольно геморройно. Теперь с помощью докера их можно запустить одной командой в консоли — например,
docker-compose up
.
Kubernetes — это менеджер облачной инфраструктуры, etcd — распределённое строго консистентное key-value хранилище, Consul — service discovery. С другими перечисленными проектами я не сталкивался, но конкретно эти проекты (и докер, конечно же) весьма большие и используются в продакшне очень многими крупными компаниями. Ничего сравнимого на D, насколько я в курсе, нет. А ещё на Go написан gogs, с помощью которого (и с помощью докера) я поднял аналог гитхаба для личных нужд за десять минут.
Не поймите неправильно, я лично Go не очень люблю, я фанат Rust, и лет пять назад я немного писал на D и даже слал им баги в багтрекер. D мне весьма понравился, особенно по сравнению с C++, на котором я тогда тоже чуть-чуть писал. Но тогда у D, например, не было нормального пакетного менеджера, не было нормальных библиотек почти ни для чего, и были очень противные баги в компиляторе, которые долго не чинились. Поэтому лично меня не зацепило сильно. Если бы сейчас не было Rust'а, есть вероятность, что я бы писал какие-то свои проекты на D.
LK4D4
31.03.2016 22:07+1Отличная статья, спасибо! Было бы неплохо еще добавлять комментарии в код для новичков.
AterCattus
01.04.2016 12:45Мне манера подачи, безотносительно к конкретным ЯП, напоминает посты вида "как из php сделать почти java", "как в delphi добавить почти async/await", и т.п.
Берем язык со своей экосистемой, пытаемся из него сделать что-то, чего он не может и не предназначен для этого, получая в лучшем случае что-то внешне похожее на уровне исходного кода, и вуаля.
mirrr
Я часто так делаю:
Конечно, если эта структура не экспортируется из пакета, тогда открытые методы Lock и Unlock ни к чему.
JekaMas
Полностью согласен. В примере надо было использовать композицию — единственно верная абстракция тут.
И наверное стоит использовать RWMutex. Непонятно мне лично зачем в примере статьи блокировка при получении значения. Вернее, зачем тут единая блокировка с изменением значения. Мы ж должны иметь возможность читать значение в любое число конкурентных горутин, если не происходит изменение значения.
vintage
В примере как раз и используется композиция. И этот пример — не выдумкаа фанатов языкаа D, а выдержка из туториала от авторов Go. Но я согласен насчёт RWMutex в конкретно этом, частном случае.