Skip to content

Latest commit

 

History

History
426 lines (343 loc) · 14.4 KB

File metadata and controls

426 lines (343 loc) · 14.4 KB

Конкурентно програмиране II

Процеси


Image-Absolute


Съдържание

  1. Процеси в Erlang - как и защо? Малко история.
  2. Създаване на процеси
  3. Конкурентност и паралелизъм
  4. Комуникация между процеси

Процеси в Erlang - как и защо? Малко история.


Image-Absolute


Image-Absolute


  • Erlang е създаден в лаборатория на Ericsson през 80-те години.
  • Основната му идея е да е способ за писане на конкурентни програми, които трябва да могат да се изпълняват безкрайно.|

Joe Armstrong

Image-Absolute


По-добър начин за писане на Телеком програми.

Image-Absolute


Телеком програми

  • Конкурентни (едно устройство трябва да може да поддържа хиляди едновременни транзакции).
  • Толерантни към грешки и проблеми, както софтуерни, така и хардуерни.|
  • Практически нулев downtime.|
  • Кодът им да може да се заменя с по-нови версии, докато те работят.|

AXE и PLEX

Image-Absolute


AXE и PLEX

  • Erlang е трябвало да бъде нещо като PLEX.
  • Но да върви на различни типове хардуер.|
  • И да е по-бърз и лесен за писане.|

Image-Absolute


AXE и PLEX

  • Множество паралелни процеси живеят в паметта.
  • По всяко време, повечето от тях чакат събитие.|
  • Когато събитие се случи, процесът прави някакво изчисление, променя си състоянието или изпраща съобщение след което пак чака.|

AXE и PLEX

  • Процесите трябва да са много леки и лесни за създаване.
  • Процесите трябва да са част от самия език.|
  • Грешките в един процес не могат да влияят на другите процеси.|

AXE и PLEX

  • По онова време паралелизъм означава множество устройства, които работят с дадения софтуер и се възприемат като едно.
  • Тоест езикът трябва да е лесен за дистрибутивност.

Image-Absolute


Erlang!

  1. Кодът върви в процеси, които са на ниво език.
  2. Тези процеси не споделят памет - имат собствен стек и собствен heap.
  3. Много са лесни за създаване и си комуникират чрез размяна на съобщения.
  4. Лесно могат да си комуникират помежду си, дори да са на различни машини.
  5. Ако един процес 'умре', другите продължават да живеят. Може нов да го замести, зависи от стратегията.

Image-Absolute


  • Erlang започва като библиотека на Prolog за fault-tolerant and distributed програмиране.
  • Развива бързо като диалект на пролог.|
  • Първият интерпретатор на езика е на Prolog.|
  • Erlang е повлиян донякъде и от Smalltalk.|

  • Erlang не е повлиян от и не имплементира Actor модела.
  • Процесите на Erlang и актьорите имат общ предшественик - комуникацията между обекти със съобщения.|
  • Доста от идеите за Актьорите намират своят път в процесите на Erlang независимо от Actor модела. |
  • Вътрешността на един процес няма нищо общо с Actor модела. |

Robert Virding

Image-Absolute


  • Erlang се превръща от Prolog, който поддържа конкурентност в обособен език.
  • Започва да работи още един човек - Robert Virding.|
  • Двамата с Joe Armstrong оформят паралелно два интерпретатора на Erlang, написани на Prolog.|
  • Erlang придобива потребители - 3-ма човека.|

  • Процесите започват да имат специален буфер, наречен 'кутия за съобщения' Mailbox.
  • Започват да могат да създават връзки помежду си.|
  • Ако някой от тях получи грешка, друг може да бъде уведомен със специално съобщение и да реагира.|

Image-Absolute


  • В края на 1989 година, езикът е тестван и функционалността му е намерена за задоволителна.
  • Проблемът е че е много бавен.
  • Излиза ново изискване - да го направят поне 40 пъти по-бърз, което после се увеличава.

Mike Williams

Image-Absolute


  • Ражда първата абстрактна машина на Erlang, написана на C - JAM.
  • Преди C и други езици са разглеждани, и други абстрактни машини са разучавани.|
  • Mike Williams е с доста повече опит от Joe в C, затова той написва JAM.|

Image-Absolute


  • 90-те години бележат началото на изхвърляне на доста Prolog синтаксис от Erlang
  • По добри GC стратегии.
  • Binary информация над даден размер да се пази в общ heap за даден node
  • ...

Image-Absolute


  • През 1993 Bogumil (Bogdan) Hausman създава TEAM, прекръстена после на BEAM.
  • Доста по-оптимизирана машина за изпълнение на Erlang bytecode.

Две хубави неща се случват за Erlang:

  1. В края на 1995, проекта за AXE-N устройствата се сгромолясва.
  2. През 1998 година Ericsson Radio AB забранява Erlang за ползване.

Image-Absolute


OTP

  • Множество малки помощни библиотеки на Erlang.
  • Design Patterns за програмиране на често желани програми.
  • Документация, курсове и How to-та
  • Mnesia/ETS бази данни

Image-Absolute


Joe Armstrong нарича Erlang език за конкурентно-ориентирано програмиране, като се базира на няколко правила.


  • Системата е изградена от процеси.
  • Процесите не споделят нищо.|
  • Процесите си комуникират чрез асинхронно изпращане на съобщения.|
  • Процесите са изолирани един от друг.|

Заключение:

  1. 1986 : Erlang е декларативен език с добавена способност за конкурентно изпълнение.
  2. 1995 : Erlang е функционален език с добавена способност за конкурентно изпълнение.
  3. 2005 : Erlang е конкурентно-ориентиран език, който се състои от комуникиращи си компоненти, написани на функционален език.

Image-Absolute


Създаване на процеси

Image-Absolute


spawn(fn ->
  <изрази>
end)

execute_after_action = fn (action, milliseconds) ->
  :timer.sleep(milliseconds)
  result = action.()
  IO.puts(result)
end

execute_after_action.(fn -> "Awake!" end, 1000)

spawn(fn -> execute_after_action.(fn -> "Awake!" end, 1000) end)
IO.puts("Sleeping...")

Image-Absolute


Друга форма на spawn е spawn/3. Тази функция има три аргумента, често наричани MFA.


defmodule Executor do
  def action_after(action, milliseconds) do
    :timer.sleep(milliseconds)
    IO.puts(action.())
  end
end

spawn(Executor, :action_after, [fn -> "Finally!" end, 1000])

Конкурентност и паралелизъм

Image-Absolute


  • Когато стартираме Elixir, той върви в един OS process или една BEAM инстанция, която наричаме node.
  • За всяко ядро на CPU-то си, обикновено получаваме по една OS-level нишка.|
  • Във всяка такава нишка се изпълнява нещото, наречено Scheduler.|
  • Scheduler-ите обикновено са обвързани с ядро на процесора, но е възможно и да ги сменят.|

  • Един Scheduler управлява опашка, наречена run queue.
  • Като цяло това е приоритетна опашка от Elixir процеси и портове.|
  • Това значи че ако имаме четири Scheduler-а, е възможно да имаме четири паралелни Elixir-level процеса.|

Image-Absolute


  • При spawn се създава процес и се поставя в някоя от опашките.
  • Един Elixir-level процес е голям около 1KB-2KB при създаването си.|
  • Можем да създаваме огромен брой процеси без да се притесняваме. Говорим за милиони.|

  • Процесите имат право на до N редукции.
  • Всяка операция свързана с процес е редукция.|
  • Когато текущо-изпълняващ се процес изчерпа редукциите си или пък е в очакване на нещо и не прави нищо, той става неактивен.|
  • По тежките операции са по скъпи.|

  • Възможно е процес да смени опашката си и да започне да се управлява от друг Scheduler.
  • Има сложен алгоритъм за балансиране на натоварването между ядрата.|
  • Често ако Scheduler остане без работа може да си 'поиска' процеси.|

Image-Absolute


Preemptive (превантивна стратегия)

Image-Absolute


Комуникация между процеси

Image-Absolute


Има три основни функции за работа с процеси:

  • spawn ги създава.
  • send изпраща съобщение до процес.
  • receive чака за съобщения към текущия процес.

pid = spawn(action)

send(pid, message)

pid = spawn(fn ->
  receive do
    pattern1 -> action1
    pattern2 -> action2
    ....
    patternN -> actionN
  end
end)

send(pid, pattern2)

@[1-8] @[10] @[2-7]


pid = spawn(fn ->
  receive do
    :say_hi -> IO.puts("Hi!")
    :say_bye -> IO.puts("Bye!")
    {:say, name, msg} -> IO.puts([name, " says ", msg])
  end
end)

send(pid, {:say, "Arnold", "I'll be back!"})

@[1-7] @[9] @[2-6] @[5]


  • Receive е като case, който се изпълнява върху полученото съобщение.
  • Съобщението може да е всякакъв тип.
  • Можем да изпратим PID-а на процеса, който извиква send и да го използваме за да получим отговор.

pid = spawn(fn ->
  receive do
    {sender, :ping} when is_pid(sender) ->
      send(sender, {self(), :pong})
  end
end)

send(pid, {self(), :ping})
IO.puts("Let's wait for a pong!")

receive do
  {sender, :pong} when is_pid(sender) ->
    IO.puts([inspect(sender), " sends PONG!"])
end

@[1-6] @[8] @[2-5] @[3] @[11-14]

@[4] @[12]


Паралелен Enum.map без да ползваме Task Image-Absolute


1..50 |> Enum.map(fn x -> x * x end)

defmodule PEnum do
  def map(enumerable, map_func) do
    enumerable
    |> Enum.map(spawn_func(map_func))
    |> Enum.map(&receive_func/1)
  end
end

@[4] @[5]


defp spawn_func(map_func) do
  current_pid = self()
  fn x ->
    spawn(fn ->
      send(current_pid, {self(), map_func.(x)})
    end)
  end
end

defp receive_func(pid) do
  receive do
    {^pid, result} -> result
  end
end

@[1-8] @[3] @[4-6] @[5] @[2] @[5]

@[10-14] @[11-13] @[12]


1..50 |> Enum.map(fn x -> :timer.sleep(1000); x* x end)
# След около 50 секунди ще имаме квадратите

1..50 |> PEnum.map(fn x -> :timer.sleep(1000); x* x end)
# След около секунда ще имаме квадратите

Image-Absolute


  • Процесите в Elixir са много леки и лесни за употреба
  • Не споделят данни.
  • Могат да се изпълняват паралелно.

Край

Image-Absolute