суббота, 16 апреля 2011 г.

Прочитал вчера вот эту статью: http://easy-coding.blogspot.com/2011/04/go.html
Итак: данная программа берет TAR с исходниками, распаковывает его, и каждый файл прогоняет через компилятор. Сразу скажу, цель того, что я все это пишу тут, это продемонстрировать (и не более того), как просто и удобно на Go можно писать многопоточные императивные программы.
и не впечатлился. Эта программа реализует простую вещь - Master/Worker pattern. Каких-либо особых преимуществ языка Go в этом нелегком деле я не заметил. Для написания этой программы, язык программирования вообще не важен, он должен позволять запускать потоки и использовать блокирующие очереди, вот и все. Все тоже самое может быть написано на C++, с использованием класса tbb::concurrent_queue, или на C#, с использованием класса BlockingCollection. Код будет выглядеть так же просто.
Мало того, на шарпе, можно написать как-то так:

using(var tar = new TarReader(tarname)) 
{
    var files = tar.NewReader(tmpdir);
    Parallel.ForEach(files, file => Compiler.Run(file, params));
}

(далее, имена всех классов и методов вымышлены, все совпадения - случайны)
 Допустим, tar - объект, читающий tar архив, метод NewReader(tmpdir) - возвращает IEnumerable, при обходе которого на диске, в каталоге tmpdir, будут создаваться новые файлы, а итератор будет возвращать их имена. При вызове tar.Dispose() - все временные файлы должны быть удалены.
Допустим, у нас есть класс Compiler, со статическим методом Run, который получает имя файла и набор флагов, в виде строк, запускает компилятор, дожидается когда он отработает и завершает работу.
Вот собственно и все. TPL не будет создавать 100500 потоков, количество выполняемых параллельно задач будет зависеть от количества процессоров. Возиться с очередями и балансировкой нагрузки - не нужно. Обработка ошибок - проще некуда.
Возникает логичный вопрос - зачем этот Go вообще нужен? :)

7 комментариев:

Александр комментирует...

C# - это, как говорится, managed environment. Сравнивать чего либо тут бессмысленно. Когда говоря C#, мы будем иметь ввиду только native код, тогда можно будет начать сравнивать.

С++ и всякие нестандарные велосипеды типа tbb::. Пусть даже в новом С++ есть и потоки, и очереди, и примитивы синхронизации, все это нашлепка в виде библиотеки, не более того. В Go - "горутина" - это не поток в понимании pthread. Поток в Go - это как поток в Erlang. Их может быть гораздо больше, чем дает API операционной системы. А мультиплексирование между логическими потоками (горутинами) и физическими потоками делается прозрачно рантаймом Go. Поэтому тут нет такого бреда, как в Java, когда нельзя пускать слишком много потоков, а то VM умрет, и люди изобретают всякие executor'ы.

Функции. В Go функции - это объекты высшего порядка. Их можно передавать по каналам, как и данные. От позволяет в диспетчер передавать не данные, а функцию worker, например. Рисовать такое же на функторах, указателях, bind()'ах - занятие крайне неприятное.

Каналы. Канал гораздо удобнее семафоров, условных переменных и очередей. В С++ их нет.

Просто ради интереса, было бы интересно посмотреть на законченную программу на С++, даже на новом С++, которая делает то же самое, что моя на Go ;-). В принципе, можно и boost использовать, и любую другую либу. Лично я не верю, что это можно существенно помочь.

Lazin комментирует...

По поводу C# - а собственно какая разница? И там и там сборка мусора, ngen тоже пока никто не отменял. Есть задачи для которых не подходит C#, но для них и Go подходить не будет.
В принципе я в курсе, что такое горутины и каналы, читал спецификацию когда он только появился и начал набирать популярность. Насколько я знаю, сейчас goroutines реализованы на основе обычных потоков, хотя может быть эти данные уже устарели.

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

Но поинт даже не в этом, а в том, что моя версия - декларативная. По сравнению с TPL, каналы и горутины - это низкоуровневые детали реализации :)

Программирование на C# с использованием TPL больше похоже на расстановку аннотаций. Если какие то вещи могут быть выполнены параллельно, мы их заворачиваем в task-и и все. Никаких потоков, никаких каналов, планировщик сам разберется как выполнить таски максимально эффективно. В моем примере таски даже не создаются явно, они создаются внутри Parallel.ForEach :)

Lazin комментирует...

Написал пример кода на с++

http://pastebin.com/kwq0cj4v

там используется обычная очередь и condition_variable из буста, для синхронизации доступа к ней. Конечно это не очень эффективно с точки зрения производительности, просто мне лень было скачивать tbb :)

Александр комментирует...

В тут еще поковырялся немного - http://easy-coding.blogspot.com/2011/04/c-go.html. Может будет интересно.

Lazin комментирует...

Написал пример кода на с++

http://pastebin.com/kwq0cj4v

там используется обычная очередь и condition_variable из буста, для синхронизации доступа к ней. Конечно это не очень эффективно с точки зрения производительности, просто мне лень было скачивать tbb :)

Александр комментирует...

C# - это, как говорится, managed environment. Сравнивать чего либо тут бессмысленно. Когда говоря C#, мы будем иметь ввиду только native код, тогда можно будет начать сравнивать.

С++ и всякие нестандарные велосипеды типа tbb::. Пусть даже в новом С++ есть и потоки, и очереди, и примитивы синхронизации, все это нашлепка в виде библиотеки, не более того. В Go - "горутина" - это не поток в понимании pthread. Поток в Go - это как поток в Erlang. Их может быть гораздо больше, чем дает API операционной системы. А мультиплексирование между логическими потоками (горутинами) и физическими потоками делается прозрачно рантаймом Go. Поэтому тут нет такого бреда, как в Java, когда нельзя пускать слишком много потоков, а то VM умрет, и люди изобретают всякие executor'ы.

Функции. В Go функции - это объекты высшего порядка. Их можно передавать по каналам, как и данные. От позволяет в диспетчер передавать не данные, а функцию worker, например. Рисовать такое же на функторах, указателях, bind()'ах - занятие крайне неприятное.

Каналы. Канал гораздо удобнее семафоров, условных переменных и очередей. В С++ их нет.

Просто ради интереса, было бы интересно посмотреть на законченную программу на С++, даже на новом С++, которая делает то же самое, что моя на Go ;-). В принципе, можно и boost использовать, и любую другую либу. Лично я не верю, что это можно существенно помочь.

milo hoffman комментирует...

почему не очень эффективно?