18. Конкурентност

18. Конкурентност

18. Конкурентност

7 януари 2013

Днес

Disclaimer

Няма да говорим за паралелни алгоритми и други подобни неща

Ще говорим за конкурентност на по-практическо ниво

IO-bound vs. CPU-bound

Processes vs. Threads (Green & Native)

fork

Системен примитив

child_id = fork()
if child_id
  puts "This is the parent"
else
  puts "This is the child speaking!"
end

fork (2)

Има и друга версия:

fork do
  puts "This is the child"
end

puts "This is the parent"

module Process

Всъщност, повечето неща са в модул `Process`:

Process.fork
Process.wait
Process.waitall
Process.waitpid

Process.wait

Process.wait чака някое от децата да приключи и връща pid-а му, а $? съдържа Process::Status.

fork { exit 99 } # 30188
Process.wait     # 30188
$?.exitstatus    # 99

Process.wait2

Process.wait2 е сходно, но връща масив от pid и Process::Status:

fork { exit 99 } # 30197
Process.wait2    # [30197, #<Process::Status: pid 30197 exit 99>]

Process.waitpid

Process.waitpid(pid) чака детето конкретно дете да приключи:

pid = fork do
  sleep 1
  puts "Child"
end

Process.waitpid(pid)
puts "Parent"

Process.waitall

Process.waitall чака всички деца да приключат

fork { sleep 1; puts "1" }
fork { sleep 2; puts "2" }

Process.waitall
puts "3"

Process.exec

Process.exec заменя текущия процес с изпълнение на команда:

fork do
  exec('ls')
  puts "Unreachable code"
end

Process.daemon

Process.daemon "откача" процеса от терминала и го пуска в background.

fork do
  Process.daemon
  loop do
    system "echo Spam >> ~/Desktop/spam"
    sleep 1
  end
end

Process.methods

Нишки

Thread.new

Създаването на нишка в Ruby е лесно:

thread = Thread.new do
  puts "This is run in the thread"
  puts "The thread is started immediatelly"
end

puts "This is run in the main thread"

Thread#join

Процесът приключва, когато основната нишка приключи. Ако искате да изчакате някоя от създадените нишки да приключи преди процеса да излезе, ползвайте Thread#join.

thread = Thread.new do
  sleep 1
  puts "Thread done"
end

thread.join
puts "Process done"

Thread#value

Thread#value блокира докато нишката не приключи и връща последния оценен израз

thread = Thread.new do
  2 + 2
end

thread.value # 4

Изключения

Ако една нишка предизвика изключение, то няма да убие интерпретатора. Вместо това, ще се появи в нишката, извикваща #value или #join.

thread = Thread.new do
  raise "Oh noes!"
end

thread.join # error: RuntimeError

Изключения (2)

Можете да промените последното с Thread.abort_on_exception.

Thread.abort_on_exception = true

Thread.methods

Thread#priority

Променливи (1)

Променливи дефинирани в блога на нишката са (очевидно) локални за нишката

thread = Thread.new { something = 1 }
thread.join

something # error: NameError

Променливи (2)

Блокът на нишката вижда променливите отвън.

answer = 1
thread = Thread.new { answer = 2 }

thread.join
answer # 2

Променливи (3)

Можете да подавате стойности на нишката през Thread.new

n = 10
thread = Thread.new(n) do |number|
  n      # 20
  number # 10
end
n = 20

thread.join

Променливи (4)

Всяка нишка функционира като хеш от символи. Така може да правите thread-local променливи

Thread.current[:x] = 10
thread = Thread.new do
  Thread.current[:x] # nil
  Thread.current[:x] = 20
end
thread.join
Thread.current[:x]   # 10

Синхронизация на нишки

Mutex (1)

Mutex (2)

$mutex = Mutex.new

def stuff
  # pre-lock
  $mutex.lock
  # critical
  $mutex.unlock
  # post-lock
end

t1 = Thread.new { loop { stuff } }
t2 = Thread.new { loop { stuff } }

t1.join
t2.join

Mutex (3)

$mutex = Mutex.new

def stuff
  # pre-lock
  $mutex.synchronize do
    # critical
  end
  # post-lock
end

t1 = Thread.new { loop { stuff } }
t2 = Thread.new { loop { stuff } }

t1.join
t2.join

Въпроси