Философия UNIX или fork() vs CreateThread()

Вот уже вторую неделю в Москве царит невыносимая жара. Вентиляторы и кондиционеры в магазинах не найти, прохладительные напитки не помогают. Уровень желания работать (УЖР) по десятибалльной шкале — «ноль точка ноль», мысли путаются, кодинг не идет. Поэтому сегодняшний пост будет «на философскую тему» и почти не будет содержать кода.

Идею для этого поста подсказали мне вы, дорогие читатели блога. Если бы не ваши вопросы в комментариях (почему wget, а не LWP и почему fork(), а не pthread_*), его бы точно не было. Или он появился значительно позже. Так что большое вам спасибо!

Наверняка вы знаете или по крайней мере слышали о философии UNIX. Но если вдруг не слышали или подзабыли, всегда можно ознакомиться с соответствующей страницей в Википедии. Согласно ей:

Философия UNIX — это набор культурных норм и философских подходов к разработке программного обеспечения, основанных на опыте ведущих разработчиков операционной системы UNIX.

Я бы сказал, «многолетнем опыте разработчиков ПО под ОС семейства UNIX».

Философия UNIX гласит:

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

Звучит интересно, но как это работает на практике? Рассмотрим пример. Пусть у нас есть скрипт на Perl, который тянет некую страницу из интернета и парсит ее. Есть по крайней мере два способа сделать это — использовать либо утилиту wget (или любой аналогичный даунлоадер), либо perl-модуль LWP. Тут есть над чем подумать, так что обратимся к многолетнему опыту unix-программистов.

Так, давайте-ка посмотрим. «Работать вместе», «использовать текстовые потоки» — похоже, что по философии UNIX нужно сделать выбор в пользу wget. И действительно, wget имеет неоспоримые преимущества перед LWP. Во-первых, загрузить файл по протоколу HTTP или FTP с помощью wget можно, написав всего лишь одну строчку кода:

1
2
3
4
#!/usr/bin/perl
use strict;

my $data = `wget -q http://example.ru/ -O -`;

В случае же с LWP нужно намного больше кода:

1
2
3
4
5
6
7
#!/usr/bin/perl
use strict;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
my $q = HTTP::Request->new(GET => 'http://example.ru/');
my $data = $ua->request($q);

Пока не особо убедительно, верно? А теперь давайте представим, что нам нужно скачать несколько страниц, список которых хранится в файле. Или рекурсивно загрузить содержимое всего сайта. У wget на оба случая предусмотрены специальный ключи командной строки — «-i» и «-r». А в случае с LWP нам придется писать дополнительный код и довольно много.

Но как же так получилось, что в модуле LWP не оказалось нужных нам функций? Просто разработчики этого модуля не следовали философии UNIX, а мыслили примерно так: «Вряд ли это кому-то понадобится, а если понадобится, напишет самостоятельно».

Смотрите-ка, что происходит. Мы используем всего лишь одну программу (wget), которая делает что-то одно (качает файлы) и делает это хорошо (ключи «-i» и «-r»). Прямо согласно «первому правилу» философии UNIX. Используя wget в своей программе, мы смогли существенно уменьшить объем кода, а значит и уменьшить время разработки, время отладки, сделать программу более простой и понятной.

Вы спросите, а как же быть, если мы хотим использовать наш скрипт как под UNIX, так и под Windows? Это будет не просто, но если установить wget.exe и сделать это правильно, скрипт будет прекрасно работать и под Windows безо всяких изменений. Но тут может возникнуть проблема производительности.

Дело в том, что в Windows, в отличии от UNIX, создание нового процесса — довольно «дорогая» операция. Мир Windows — это монолитные приложения, динамические библиотеки, CreateThread() и семафоры, в то время, как мир UNIX — это небольшие утилиты, fork(), сигналы и пайпы. Да, в Windows тоже есть пайпы и дочерние процессы, ровно как и в UNIX есть нити и библиотеки, и это позволяет более-менее комфортно писать кроссплатформенные приложения. Но идеология остается разной.

Так какая же идеология более удачная? Что выгоднее для программистов и пользователей? Лично мне больше по душе unix way, потому что он проще. И следуя ему намного проще как писать программы, так и отлаживать их. Программу, вызываемую с помощью fork-exec и имеющую ключ «-v» легко отладить даже без помощи gdb, а вот найти ошибку в многопоточном windows-приложении без OllyDbg бывает ой как не просто, я проверял! Мне иногда даже кажется, что ООП, UML, отладчики и все остальное придумали исключительно для Windows.

Хочу отметить, что все написанное — это лишь мое мнение, человека, пару месяцев назад закончившего институт. Не исключено, что когда-нибудь оно изменится. И пожалуйста, поймите меня правильно, я не фанатик философии UNIX. Судя по тому, что Winodws 7 и Ubuntu — вполне себе работоспособные операционки с кучей приложений, право на жизнь имеет каждый из подходов.

Дополнение: См также заметки Чем хорош и чем плох Linux? и Аналоги Windows-программ для UNIX.

Подпишитесь на блог с помощью RSS, E-Mail, Google+ или Twitter!
Также, пользуясь случаем, приглашаю вас посетить мой форум.

  • http://twitter.com/Saint_Byte Saint_Byte

    Хм ихмо в никсах библиотеки гораздо документированее чем под виндой . Винда действительно один геморой

  • http://eax.me/ Безумный Программист

    Ну тут все зависит от производителя. Windows API прекрасно документирован — http://msdn.microsoft.com/ru-ru/default.aspx Прочие библиотеки может быть невыгодно документировать, если они поставляются с платным ПО.

  • http://bolverin.com/ BOLVERIN

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

  • http://eax.me/ Безумный Программист

    Удобная позиция, Вам не кажется? Ну раз Вы ее выбрали, прошу по два примера на каждый случай — когда лучше использовать unix-подход, а когда windows-подход?

  • http://bolverin.com/ BOLVERIN

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

  • http://eax.me/ Безумный Программист

    Да, такие задачи бывают. Только я что-то не совсем понимаю, какое это отношение имеет к сабжу?

  • http://bolverin.com/ BOLVERIN

    философия никсов это небольшой набор правил для написания кода. вроде так? т.е. имеет самое прямое отношение

  • http://eax.me/ Безумный Программист

    Вы в очередной раз взрываете мой мозг :) Предлагаю сойтись на том, что Вы правы :)

  • http://bolverin.com/ BOLVERIN

    ыч. а чего взрываю то? объясняй! :)

  • http://eax.me/ Безумный Программист

    Не, я пасс :)

  • http://bolverin.com/ BOLVERIN

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

  • http://www.google.com/profiles/randomclear Александр

    >>> Мы используем всего лишь одну программу (wget), которая делает что-то одно (качает файлы) и делает это хорошо (ключи «-i» и «-r»).

    Как насчёт сравнить с этой точки зрения многофункциональные API Windows с куцыми параметрами в API Unix? Тот же CreateProcess, чтоб далеко не ходить.

    >>> Лично мне больше по душе unix way, потому что он проще

    Окей, разве не это сделали разрабы LWP, убрав всё лишнее? ;)

    Итого: сказано, в общем-то верно, но вот примеры как-то хромают.

  • http://www.google.com/profiles/randomclear Александр

    Можно ссылку на аналог MSDN Library?

  • http://eax.me/ Безумный Программист

    Перечитал ветку. Честно говоря, какой-то беспредметный разговор получился, и не без моей вины. С вашим мнением согласен.

  • http://eax.me/ Безумный Программист

    >> убрав всё лишнее
    Лишним было бы наличие у wget возможности разархивировать скачиваемые архивы или проверять их md5. Рекурсивное скачивание — совсем не лишний функционал, пару раз он меня уже спасал.

    >> сравнить [...] API Windows с [...] API Unix

    А при чем тут вообще API и число аргументов? Ну раз Вы спросили, Windows API я считаю намного хуже POSIX ввиду наличия большого числа лишних аргументов. Например, я хочу открыть файл. В UNIX все просто — fopen(«file.txt, «r») означает открыть файл на чтение. В Windows — CreateFile то ли с семью то ли с 9-ю аргументами, порядок и значение которых мне что сейчас, что пару лет назад, когда я этим делом активно увлекался, вспомнить без MSDN не удается. Делайте выводы.

  • http://www.google.com/profiles/randomclear Александр

    >>> А при чем тут вообще API и число аргументов?

    Да просто ткнул, что Unix тоже не всегда следует этой идеологии.

    К примеру, почему-то когда идеология Unix («делать что-то одно и хорошо») частично воплощается и в других системах (функциональный CreateProcess в Windows — очевидно, что он «запускает процессы и делает это хорошо»; ну или CreateFile — обе они имеют много опций, аналогично с разнообразием ключей у wget), то это всё равно отстой («Windows API я считаю намного хуже POSIX ввиду наличия большого числа лишних аргументов» — это, кстати, далеко не только ваша точка зрения), ибо (видимо) просто не рассово верная ОС.

    >>> Делайте выводы.

    Окей, делаю:

    >>> Рекурсивное скачивание — совсем не лишний функционал, пару раз он меня уже спасал.

    Открыть файл на чтение с возможностью указания блокировать запись в него или нет отношу к «совсем не лишний функционал, пару раз он меня уже спасал».

    >>> порядок и значение которых мне что сейчас, что пару лет назад, когда я этим делом активно увлекался, вспомнить без MSDN не удается.

    У wget куча аргументов, «порядок и значение которых мне что сейчас, что пару лет назад, когда я этим делом активно увлекался, вспомнить без man не удается».

    >>> В UNIX все просто — fopen(«file.txt, «r»)

    Т.е. возможность рекурсивного скачивания у wget — это полезная и не лишняя возможность, а возможности CreateFile — это аналог «возможности разархивировать скачиваемые архивы или проверять их md5″, так что-ли?

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

  • http://eax.me/ Безумный Программист

    Разница в том, что wget — это программа, а CreateProcess — функция. У wget один обязательный и вполне очевидный аргумент — URL (или ни одного, но тогда мы увидим help), а у CreateProcess — 10 (подсмотрел в http://msdn.microsoft.com/en-us/library/ms68242… ;). Кроме того польза от

    >> указания блокировать запись в него или нет

    весьма сомнительная. Как сделано в UNIX — хотим создать файл, говорим fopen. Хотим сменить права доступа — говорим chmod, владельца — chown. У каждой функции вполне очевидное число, порядок и значение аргументов.

    Так что Ваша попытка
    1) Утверждать, что разработчики Windows следуют философии UNIX
    2) Сравнивать API с программами, словно яблоки с грушами
    3) Забыть про «Упрощайте, насколько это возможно, но не более того»

    куда более непоследовательная, Вам не кажется?

  • http://eax.me/ Безумный Программист

    >> Да просто ткнул, что Unix тоже не всегда следует этой идеологи

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

  • Sugar

    wget — это очень маленькая и в тоже время очень мощная утилита, с кучей параметров и возможностей, как раз из области unix-way. В этом с автором согласен.
    Но, когда я пишу код на перле, я стараюсь писать код на перле, вот это вот по-моему портит код: `any_command —params`. Но хотя для небольших скриптов wget вполне годится, и наверное, да, согласен с автором работает лучше lwp.
    LWP — программный каркас, написанный на перле, это просто огромная библиотека классов, модулей, утилит и т.п.. Вот выдержка из документации к нему:

    The libwww-perl collection is a set of Perl modules which provides a simple and consistent application programming interface (API) to the World-Wide Web. The main focus of the library is to provide classes and functions that allow you to write WWW clients. The library also contain modules that are of more general use and even classes that help you implement simple HTTP servers.

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

  • sugar

    API Windows — кривое поделие с убогой документацией, Вам наверно не доводилось программировать.

  • http://eax.me/ Безумный Программист

    И Вам спасибо за Ваши комментарии и интерес с посту!

  • Nickolas

    По поводу UNIX-way — прекрасно работает в общих случаях. Но не следует забывать о частных случаях, в которых строго (слепо) следуя UNIX-way только ради философии (религии) может повлечь за собой кучу проблем с разработкой.

    «Мир Windows – это монолитные приложения, динамические библиотеки, CreateThread() и семафоры, в то время, как мир UNIX – это небольшие утилиты, fork(), сигналы и пайпы.»

    В UNIX также есть монолитные приложения (и снова таки в некоторых _частных_ случаях работают лучше чем не монолитные). Аналоги тех же CreateThread() и семафоров есть в интрефейсе pthread_*. Аналоги сигналов в windows также имеются — механизм сообщений.
    Да и в UNIX без динамических библиотек никуда не денешься — не забываем про libc, rtld и т.д.

    «Да, в Windows тоже есть пайпы и дочерние процессы, ровно как и в UNIX есть нити и библиотеки, и это позволяет более-менее комфортно писать кроссплатформенные приложения.»

    Угу, только реализация оставляет желать лучшего. Это мягко говоря. Чересчур громоздкая, перегруженная и неудобная.

    «Мне иногда даже кажется, что ООП, UML, отладчики и все остальное придумали исключительно для Windows.»

    По поводу ООП — принцип хороший, если применять _с_умом_. Отлично помогает при разработке больших проектов. Как пример — ядра современных операционных систем, таких как Linux, FreeBSD. Да, они написаны на С, но тем не менее используют парадигмы ООП. Это хорошо видно, если посмотреть на исходные коды этих ядрер. Использовать ООП и при этом писать на С — вполне возможно! :)
    То же с UML — прекрасный иснтрумент для описания сложных больших проектов. Как минимум на этапе разработки дизайна приложений.
    Отладчики — даже в UNIX они иногда нужны (все мы периодически делаем ошибки :) ). Да и отладка многопоточных приложений в любой ОС — всегда лёгкая задача (особенно когда начинаются «гонки»).

    Что хочется сказать: не стоит слепо и бездумно следовать принципам UNIX только ради того, чтобы придерживаться философии, религии и т.д.
    Например, есть задачи, в которых намного более удобно использовать потоки и общие области памяти, доступ к которым ограничивать с помощю mutex-ов, чем плодить несколько процессов и управлять механизмом SysV ShM, и иметь кучу проблем с синхронизацией.

    Вообще в мире IT-менеджмента сущетсвует так называемое «праивло 4х D»:
    1. Design
    2. Development
    3. Debugging
    4. Deployment.
    Правило гласит: «Ошибка на одном из уровней усложняет следующий уровень на порядок». Т.е. с полхим дизайном (некачественно проведённым этапом проектирования) мы усложняем этап разработки на порядок. Что в свою очередь порождает лавинообразый эффект и усложняет этапы отладки и внедрения. Почему и говорят — хорошо написанное ТЗ — 80% сделанной работы. Поскольку именно по ТЗ строится дизайн разрабатываемой системы.

    Резюмируя всё вышесказанное — всё хорошо, если с умом применять. Всё-таки не зря человечество придумало много инструментов для конкретных целей, и мало кому из нормальных людей приходит в голову забивать гвозди микроскопом или топором чинить тонкий механизм ручных механических часов.

    • Nickolas

      Пардон, опечатался.
      Отладка многопоточных приложений в любой ОС — всегда НЕ лёгкая задача.

      И добавлю: по личному опыту любой _большой_ проект всегда проще делать модульным. Но думаю это и так понятно.

  • Товарищь Я

    Ну на мой взгляд сравнивать fork и CreateThread ну очень не правильно — многопоточность и «многопрограммность» это две совершенно разные вещи. Первая позволяет эффективно распаралеливать работу — что весьма полезно в наше многопоточное время. Вторая — достигать высокой стабильности (агась, вирус из двух процессов, перезапускающих друг друга или те жй SQL Server, Oracle). Плюс одной при этом являеться минусом другой. И все же ни первое ни второе к унификации программных интерфейсов — краеугольному камню Unix, как не крути отношения все же не имеет.