mri ruby gil

78
ВОЗМОЖНОСТИ RUBY в конкурентном программировании

Transcript of mri ruby gil

ВОЗМОЖНОСТИ RUBY в конкурентном

программировании

• Обработка запроса в многопоточном сервере (Puma) может быть очень долгим

• Как реализована многопоточность в MRI Ruby

• Как работает планировщик и приоритетность

• Что внутри EventMachine

• Когда вам могут понадобиться файберы

• Дальнейшее развитие многопоточности в Ruby

Многопоточность?В Ruby?

Global Interpreter Lock(GIL)

Руби, которые смогли

JRuby

Rubinius

MRI Ruby

thread.c

MRI

model 1: Userlevel Thread Same as traditional ruby thread.

model 2: Native Thread with Global VM lock Using pthread and Ruby threads run concurrent.

model 3: Native Thread with fine grain lock Using pthread and Ruby threads run concurrent or parallel.

YARV Thread Design

MRI

model 1: Userlevel Thread Same as traditional ruby thread.

model 2: Native Thread with Global VM lock Using pthread and Ruby threads run concurrent.

model 3: Native Thread with fine grain lock Using pthread and Ruby threads run concurrent or parallel.

YARV Thread Design

MRI

model 1: Userlevel Thread Same as traditional ruby thread.

model 2: Native Thread with Global VM lock Using pthread and Ruby threads run concurrent.

model 3: Native Thread with fine grain lock Using pthread and Ruby threads run concurrent or parallel.

YARV Thread Design

Стал ли ваш код потокобезопасным?

GIL нужен для защиты внутреннего состояния MRI

MRI

model 1: Userlevel Thread Same as traditional ruby thread.

model 2: Native Thread with Global VM lock Using pthread and Ruby threads run concurrent.

model 3: Native Thread with fine grain lock Using pthread and Ruby threads run concurrent or parallel.

YARV Thread Design

immutable objects can be shared (read) across Guilds

Multi-VM?

GIL

Как устроен GIL?

0.135

fib(29)

• Сборка вьюхи• Обработка большого количества данных• …

def show render text: fib(29) end

Puma, 5 потоков

$ (curl -i localhost:3000); (curl -i localhost:3000);

HTTP/1.1 200 OK X-Request-Id: 839cc258-019b-4737-b169-60b05caaceca X-Runtime: 0.139625

HTTP/1.1 200 OK X-Request-Id: a3d85a32-53e6-4a5a-99cd-190f6f48f964 X-Runtime: 0.130910

$ (curl -i localhost:3000 &); (curl -i localhost:3000 &);

HTTP/1.1 200 OK X-Request-Id: 14183c91-7e27-4f7f-be3d-d06814fa6c55 X-Runtime: 0.278702

HTTP/1.1 200 OK X-Request-Id: 0ff1ed87-3f54-4afd-af34-218601bbafea X-Runtime: 0.276639

(curl -i localhost:3000 &) x5

1) X-Runtime: 0.682356

2) X-Runtime: 0.682923

3) X-Runtime: 0.481979

4) X-Runtime: 0.412355

5) X-Runtime: 0.414157

Почему так?и почему именно 0.682356

Thread.new { }

thread_timer(void *p)

while (system_working > 0) { /* timer function */

ubf_wakeup_all_threads(); timer_thread_function(0); if (TT_DEBUG) WRITE_CONST(2, "tick\n"); /* wait */

timer_thread_sleep(gvl); }

while (system_working > 0) { /* timer function */

ubf_wakeup_all_threads(); timer_thread_function(0); if (TT_DEBUG) WRITE_CONST(2, "tick\n"); /* wait */

timer_thread_sleep(gvl); }

while (system_working > 0) { /* timer function */

ubf_wakeup_all_threads(); timer_thread_function(0); if (TT_DEBUG) WRITE_CONST(2, "tick\n"); /* wait */

timer_thread_sleep(gvl); }

timer_thread_function(void *arg) { RUBY_VM_SET_TIMER_INTERRUPT(vm->running_thread); }

while (system_working > 0) { /* timer function */

ubf_wakeup_all_threads(); timer_thread_function(0); if (TT_DEBUG) WRITE_CONST(2, "tick\n"); /* wait */

timer_thread_sleep(gvl); }

/* 100ms. 10ms is too small for user level thread scheduling * on recent Linux (tested on 2.6.35) */

#define TIME_QUANTUM_USEC (100 * 1000)

Приоритетность

Thread 1priority = 1 priority = 0

Thread 2

12 3

if (th->priority > 0) limits_us <<= th->priority; Else

limits_us >>= -th->priority;

if (th->status == THREAD_RUNNABLE) th->running_time_us += TIME_QUANTUM_USEC; rb_thread_schedule_limits(limits_us)

100ms * 2priority

rb_thread_schedule_limits(unsigned long limits_us) { if (th->running_time_us >= limits_us) { /* … */ } }

А что если мы хотим сами управлять потоками?

Fiber

For example, on the * 32bit POSIX OS, ten or twenty thousands Fiber can be created.

• быстрое переключение (смена контекста)• ручное управление

вы можете написать свой серверна файберах и евентмашине

goliathGoliath is an open source version of the non-blocking (asynchronous)

Ruby web server framework.

Что такое EventMachine?(и причем тут файберы)

EM.run do res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p res.response } end

void EventMachine_t::Run() { while (RunOnce()) ; }

_UpdateTime(); _RunTimers();

_AddNewDescriptors(); _ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

_UpdateTime();_RunTimers();

_AddNewDescriptors(); _ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

_UpdateTime(); _RunTimers();

_AddNewDescriptors();_ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

_UpdateTime(); _RunTimers();

_AddNewDescriptors(); _ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3

end

_UpdateTime(); _RunTimers();

_AddNewDescriptors(); _ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

_UpdateTime(); _RunTimers();

_AddNewDescriptors();_ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

_UpdateTime(); _RunTimers();

_AddNewDescriptors(); _ModifyDescriptors();

_RunEpollOnce();

_DispatchHeartbeats(); _CleanupSockets();

if (bTerminateSignalReceived) return false;

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

EM.run do

fiber = Fiber.new do current_fiber = Fiber.current p 1 res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget res.callback { p 4; current_fiber.resume(5) } p 2 puts Fiber.yield p 6 end p 0 fiber.resume p 3 end

em-synchrony

Fiber• yield • resume

enumerator = Fiber.new do val = 0

loop do val += 1 Fiber.yield(val) end end

enumerator.resume # => 1 enumerator.resume # => 2 enumerator.resume # => 3

Вывод

Спасибо

achempion