Skip to content

Latest commit

 

History

History
333 lines (232 loc) · 28 KB

ProgrammingLanguageCharacteristics.md

File metadata and controls

333 lines (232 loc) · 28 KB

Оглавление

Потоки, однопоточность и многопоточность

Потоком выполнения, тредом (англ. a thread of execution, thread) называют наименьший набор инструкций, который может быть независимо обработан диспетчером операционной системы (англ. scheduler).

Однопоточным (англ. single-threaded) называют язык программирования, который имеет лишь один поток выполнения, называемый основным потоком (англ. main thread).

Примером однопоточного языка является JavaScript.

Наличие одного потока означает, что в один момент времени может исполняться только одна операция.

Если язык программирования использует несколько потоков выполнения, то его называют многопоточным (англ. multi-threaded).

Обычно многопоточные языка нацелены на серверную разработку для того, чтобы можно было распределять трудоёмкие вычисления (операции ввода-вывода, которые могут занимать продолжительное время) между несколькими потоками.

Примеры многопоточных языков: Java, Go, C#, Rust.

Благодаря асинхронной среде выполнения NodeJS, язык JavaScript так же может выполняться на сервере, а его создатели считают, что разработка с использованием нескольких потоков неэффективна и довольно сложна.

Использование нескольких потоков заставляет решать программистов проблемы параллелизма (concurrency issues), в том числе проблему блокировки потоков (dead-locking): только один поток может использовать определённый ресурс в один момент времени, другие потоки должны ожидать освобождения ресурса.

Операции ввода-вывода

Операциями ввода-выводы (англ. input/output, I/O) называют взаимодействие некоторого обработчика информации (компьютера, системы) с окружающим миром (человеком или другим компьютером).

Ввод (англ. input) — любые данные или сигналы, которые получает система.

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

Например, ниже приведён пример создания текстового файла README.md, который содержит строку Notes и имеет кодировку Unicode.

const fs = require('fs');
fs.writeFileSync('./README.md', 'Notes', 'utf-8');

Вывод (англ. output) — любые данные или сигналы, которые система отправляет окружающему миру.

Например, отображение информации на экране или в консоли являются операциями вывода.

Ниже представлен пример вывода содержимого файла, который был создан в примере выше.

const fs = require('fs');
const data = fs.readFileSync('./NodeJS.md', 'utf-8');
console.log(data.toString()); // 'Notes'

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

Блокирующие и неблокирующие операции ввода-вывода

Блокировка (англ. blocking) — ситуация, при которой следующий блок кода не может быть запущен, поскольку основной поток занят выполнением (ожиданием выполнения) предыдущего блока кода.

Операции, которые приводят к блокировке основного потока называют блокирующими операциями ввода-выввода (англ. blocking I/O). Обычно к ним относят трудоёмкие синхронные операции ввода-вывода.

Синхронное и асинхронное программирование

О синхронном программировании

Синхронным программированием (англ. synchronous programming) называют такое поведение языка программирования, при котором операции (инструкции) в некотором блоке кода выполняются последовательно (синхронно), то есть в порядке их указания в коде.

При таком подходе следующая операция не может быть запущена, пока не завершится текущая операция.

Синхронная операция ввода-вывода (англ. synchronous I/O, sync I/O) — операция ввода-вывода, которая выполняется синхронно. Основной поток дожидается окончания выполнения такой операции и только потом запускает следующую операцию.

Большинство операций являются синхронными вне зависимости от языка программирования. Например, синхронными являются операции:

  • Арифметические, логические и строковые операции.
  • Операции присваивания = и сравнения <,>,=.
  • Вызов функций и методов ().
  • Вывод на консоль.

Конструкции if..else, switch, while, for, for..of также работают синхронно.

Например, код ниже на языке JavaScript выполнится синхронно.

console.log('start');
const sum = (a, b) => a + b;
console.log(sum(1, 7));
console.log('end');
/*
> 'start'
> 8
> 'end'
*/

Об асинхронном программировании

В широм смысле под асинхронной операцией понимают операциию, которая выполнится в будущем, не прямо сейчас.

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

Асинхронная операция ввода-вывода (англ. asynchronous I/O, async I/O) — операция ввода-вывода, выполнение которой не препятствует запуску следующей операции.

Какие операции делают асинхронными в синхронном языке программирования?

Такие операции, выполнение которых может занять продолжительное время, а именно:

  • Работа с файловой системой (англ. file system). Например, запись в файл и чтение из файла.
  • Работа с сетью (англ. networking), чаще всего это HTTP-запросы, но также могут использоваться TCP, UDP, вебсокеты (англ. websockets).
  • Работа с базами данных.
  • Таймеры и другие планируемые задачи.

Как реализуется асинхронность?

При помощи функций обратного вызова. В них содержится набор инструкций, который должен быть выполнен после того, как выполнена трудоёмкая операция.

Пример асинхронного кода

Ниже представлен пример асинхронной функции setTimeout, которая создаёт таймер и по истечению указанного времени вызывает функцию обратного вызова () => console.log('2').

console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
/*
> 1
> 3
> 2
*/

Как можно заметить, setTimeout не припятствует вызову следующего console.log(). Код выполнился асинхронно (не последовательно).

Типизация

О системе типов и типизации

Система типов

Рекомендуется почитать о типах данных.

В рамках изучения языков программирования существует понятие системы типов.

Система типов (англ. type system) - такая логическая система, которая связывает некоторую переменную (область памяти, хранящую данные) с определённым типом данных. Эта связь означает, что после установления типа переменная приобретает множество допустимых значений и ограниченный набор операций над этими значениями.

Например, для переменной числового типа могут быть доступны операции инкремента и возведения в степень: x++, x^2, а для переменной строкового типа - операции поиска и получения подстроки: str.find(/* ... */), str.substring(/* ... */).

Ошибка типа

Попытка выполнить операцию над типом данных, которая выходит за пределы допустимых операций данного типа, обычно приводит к ошибке типа (англ. type error). Например, нельзя выполнить разность строк или поиск подстроки в числе.

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

Типизация и её виды

Язык программирования, использующий систему типов, называется типизированным языком (англ.typed language).

В более широком смысле слова под типизацией (англ typing) подразумевают классификацию по типам. Конкретно же в рамках изучения некоторого языка программирования под типизацией подразумевают то, каким образом система типов этого языка обрабатывает типы данных.

Типизированный язык программирования может иметь:

Например, JavaScript является типизированным языком и имеет динамическую, слабую, неявную типизацию.

Статическая и динамическая типизация

Типизированный язык в определённый момент времени производит проверку типа (англ. type checking).

Проверка типа может производиться во время компиляции (англ. compile time) или в режиме реального времени (англ. run-time), то есть по ходу выполнения программы.

При стратической типизации типы устанавливаются на этапе компиляции. К моменту выполнения программы они уже установлены и компилятор знает, где какой тип находится.

Пример языков со статической типизацией: Java, C#.

/* Java */
public class Notes {
  public static void main(String []args){
    int number = 1; // числовой тип
    number = true; // error: incompatible types: boolean cannot be converted to int
  }
}

При динамической типизации типы определяются во время работы программы.

Пример языков с динамической типизацией: Python, JavaScript.

/* JavaScript */
let a; // тип неизвестен
a = 1; // числовой тип
a = true; // логический тип

Слабая и сильная типизация

При слабой (нестрогой) типизации автоматически выполняется множество неявных преобразований типов даже при условии неоднозначности преобразования или возможности потери точности данных.

Пример языка со слабой типизацией: JavaScript.

/* JavaScript */
console.log(1 + [] + {} + 'notes'); // "1[object Object]notes"
console.log(1 - []); // 1

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

Пример языков с сильной типизацией: Java, Python.

Например, нельзя сложить число и массив.

/* Java */
public class Notes {
  public static void main(String []args){
    int number = 17;
    int array[] = new int[3];
    System.out.println(number + array); // error: bad operand types for binary operator '+'
  }
}

Явная и неявная типизация

При явной типизации тип новых переменных, функции, их аргументов и возвращаемых ими значений нужно задавать явно.

Пример языков с явной типизацией: C++, C#.

/* C++ */
int sum(int a, int b) {
    return a + b;
}

При неявной типизации эта задание типов производится автоматически компиляторами и интерпретаторами.

Пример языка с неявной типизацией: JavaScript.

let a; // неизвестно, какого типа будет значение переменной
a = 17;
a = 'Notes';
a = () => {};
a = null;
function fn (a, b) { a + b } // неизвестно, какого типа параметр функции и что она возвращает
fn(1, 7) // 8
fn (1, '7') // 17

Кроссплатформенность и нативность

Кроссплатформенностью программного обеспечения (англ. crossplatform software) называют способность программного обеспечения работать на нескольких аппаратных платформах (поддерживаются разными типами процессоров) или операционных системах (Windows/MacOS/Linux).

Нативностью программного обеспечения (англ. native software) называют программное обеспечение, которое было написано для конкретной аппаратной платформы, операционной системы. Такое ПО может быть запущенно только на своей платформе, на другой платформе такое ПО можно запустить только при помощи эмулятора, что обычно приводит к значительному снижению производительности.

Нативные приложения (англ. native application, native app) пишут на нативном языке программирования (англ. native programming languare, то есть на "родном" (характерном для платформы) языке. Поэтому нативные приложения обычно отличаются очень хорошей совместимостью и производительностью, так как используют максимум из преимуществ своей целевой платформы.