Какой язык программирования изучать на перспективу

Меня тут недавно спросили — какой язык программирования следует изучить на перспективу. Я ответил в том духе, что никаких формальных признаков того, что язык будет перспективен лет через пять, не существует и до сих пор думаю, что прав. В самом деле, если взять «моду» на языки за последние десять лет, видно, что их успех определяется подводными течениями, которые со стороны увидеть трудно, компании неисповедимы, могут «выстрелить» чем-то, что разрабатывали втихую много лет.

Правда потом, по пути домой я задумался над этим более глубоко и теперь считаю, что популярность должны набирать языки, в которых легко, на уровне языка, делается параллелизация. Дома у меня меня ремонт сейчас и где-то в груде вещей лежит компьютер, который я покупал пять лет назад, там стоит чётвёртый «Пентиум» с тремя гигагерцами, а через шесть лет максимальная частота процессоров «Интел» находится на уровне 3,8 гигагерца (может чуть больше, не принципиально).

Над цифрами выше трудятся уже только оверклокеры, заливая штаны жидким азотом. Гонка частот уже, кажется, остановлена, началась гонка за количество ядер. Недавно был выпущен первый смартфон с четырьмя процессорами, а рабочая станция «Мак Про» поставляется в топовой конфигурацией с 12 процессорами. Сегодня на «Хабре» появилась новость о процессоре GA144, который будет продаваться за шестьсот рублей. У него 144 ядра.

Не говоря уже о GPU, у которых количество ядер приближается к тысячам.

Если взять какой-то язык, который легко умеет параллелиться, имеет более-менее привычный большинству (читай «си-подобный») синтаксис, компилятор или интерпретатор которого умеет использовать GPU в нужных местах (сортировки, поиск и прочее), то он может иметь успех со временем.

Причём «легко» не обязательно даже так, как это сделано в «Гоу» (хотя и желательно) — отдельной конструкцией языка (go), нет нужды сводить к этому всю идеологию языка, как в «Эрланге», просто чтобы это делалось, в тривиальных случаях, в одну-две строки, но было гибко управляемым в случае такой потребности. Нужно понижать порог вхождения в многозадачность, к чёрту подробности, в простейших случаях это должно делаться легко и просто, пусть и не особо эффективно.

Например, на «Пайтоне» как-то так:

@fork
def sort(arr):
    arr.sort()
    return arr

 # тут функция начала фоном работать
result = sort(somearray)

… много кода …

# а тут уже доступны результаты, если недоступны, то будем ждать
print([el for el in result if el])

Да, это не особо эффективно, нас уже приучили разбрасываться памятью, но ещё не приучили с лёгкостью разбрасываться ядрами, ничего привыкнем, когда их станет, как грязи.

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

9 комментариев

Многозадачность в «Гоу»

В прошлой своей записи я упомянул многозадачность в «Гоу», а сейчас вспомнил, что я ни разу не показывал как же она там выглядит с точки зрения простого программиста. Покажу на примере.

Предположим, нам нужно вычислить какой-то сложный хеш (я выбрал sha512) от трёх файлов (у меня вычисляется от одного и того же — файла программы), это может занять большое количество времени, а у нас как раз компьютер с несколькими ядрами, чего им простаивать-то? Дадим каждому ядру до заданию, пусть считают.

package main

import (
    "io"
    "os"
    "crypto/sha512"
    "encoding/hex"
    "runtime"
    "strconv"
)

func main () {
    ch := make(chan string)

    N := 3

    runtime.GOMAXPROCS(N)

    for i := 0; i<N; i++ {
        go func (name string, num int, ch chan string) {
            if f, err := os.OpenFile(name, os.O_RDONLY, 0666); err == nil {
                defer f.Close()
                h := sha512.New()

                io.Copy(h, f)

                ch <- strconv.Itoa(num) + ". " + hex.EncodeToString(h.Sum())
            } else {
                panic("Cannot open file")
            }
        }(os.Args[0], i, ch)
    }

    for ; N > 0; N-- {
        print(<-ch, "\n")
    }
}

Что здесь происходит.

Первая строка объявляет имя модуля, для основного модуля программы это имя должно быть «main», эта конструкция скажет компилятору, что именно тут находится входная точка нашей программы. На первый взгляд кажется, что в основном модуле можно было бы просто не указывать имя, но язык придерживается хорошего принципа «явное лучше неявного», чем он мне очень симпатичен.

Дальше, очевидно, подключаются модули, в языке базовых функций почти нет, поэтому даже для такой небольшой программы мне понадобилось шесть модулей: «io» для копирования данных из одного потока в другой, «os» для работы с файлом, «crypto/sha512» для хеширования, «encoding/hex» для кодирования получившейся хеш-суммы в шестнадцатеричное представление, «runtime» чтобы выставить сколько го-программ будет выполняться параллельно и «strconv» чтобы сконвертировать число в строку.

Функция main — входная точка основного модуля программы, именно она (как в «Си») вызывается в самом начале.

Далее я создаю (make) небуферизированный «канал» и объявляю, что по нему будут идти строки, конструкция «:=» многим напоминает Паскаль, но отношения к нему не имеет, это синтаксический сахар для создания локальной переменной. «Канал» — это хорошее, говорящее название, хорошо отображающее суть, это действительно канал общения го-программы с чем-то извне.

В цикле создаются и сразу запускаются в фоне (конструкцией «go») на выполнение N го-программ, которым передаётся имя файла от которого надо считать хеш, номер функции в цикле и переменная «канала» для общения.

Внутри го-программы открывается файл, он копируется во входной поток хеширующей функции, считается хеш, конвертируется в шестнадцатеричное значение и отправляется (конструкцией «ch <- smth») в «канал».

Цикл не ждёт выполнения го-программы, а просто запускает их в фоне.

Дальше идёт второй цикл, где N раз запускается команда на чтение из канала („… <- ch»).

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

У такой лёгкости использования есть свои ограничения, но об этом как-нибудь в другой раз.

15 комментариев