최근 Ruby를 배우고 있는데, 배운 내용을 요약해서 공유하고 싶습니다. 다음 글에서는 주로 Ruby의 동시성 및 전역 잠금에 대한 관련 정보를 예제 코드를 통해 자세히 소개합니다. 참고할 수 있으니 아래를 살펴보자.
머리말
이 글은 루비 동시성과 전역 잠금에 대한 관련 내용을 주로 소개하고 참고 및 학습을 위해 공유합니다. 아래에서는 많은 말을 하지 않겠습니다. 자세한 소개.
동시성과 병렬성
우리는 종종 동시성과 병렬성이라는 두 가지 개념을 접하게 됩니다. 동시성과 병렬성을 언급하는 거의 모든 기사에서는 동시성이 병렬성과 동일하지 않다는 사실을 언급합니다. 이 문장을 어떻게 이해해야 할까요?
동시성: 셰프는 손님 2명이 주문한 메뉴를 동시에 받아 처리해야 합니다.
순차 실행: 셰프가 한 명만 있으면 그 사람만 갈 수 있습니다. 하나의 메뉴가 완성되었습니다.
병렬 실행: 두 명의 셰프가 있으면 동시에 요리할 수 있고 두 사람이 함께 요리할 수 있습니다.
이 예를 웹 개발로 확장하면 다음과 같이 이해할 수 있습니다.
동시성: 서버는 두 클라이언트가 시작한 요청을 동시에 받았습니다.
순차 실행: 서버에는 요청을 처리할 프로세스(스레드)가 하나만 있고 두 번째 요청은 첫 번째 요청까지 완료할 수 없습니다. 요청이 완료되었으므로 두 번째 요청은 기다려야 합니다.
병렬 실행: 서버에는 요청을 처리하는 두 개의 프로세스(스레드)가 있으며 두 요청 모두 순서 문제 없이 응답할 수 있습니다.
에 따르면 위에서 설명한 예는 Ruby에서 이러한 동시 동작을 시뮬레이션하는 방법은 무엇입니까? 다음 코드를 살펴보십시오.
1. 순차 실행:
스레드가 하나만 있는 경우 작업을 시뮬레이션합니다.
require 'benchmark' def f1 puts "sleep 3 seconds in f1\n" sleep 3 end def f2 puts "sleep 2 seconds in f2\n" sleep 2 end Benchmark.bm do |b| b.report do f1 f2 end end ## ## user system total real ## sleep 3 seconds in f1 ## sleep 2 seconds in f2 ## 0.000000 0.000000 0.000000 ( 5.009620)
2. 병렬 실행
멀티스레딩 중 작업을 시뮬레이션합니다.
# 接上述代码 Benchmark.bm do |b| b.report do threads = [] threads << Thread.new { f1 } threads << Thread.new { f2 } threads.each(&:join) end end ## ## user system total real ## sleep 3 seconds in f1 ## sleep 2 seconds in f2 ## 0.000000 0.000000 0.000000 ( 3.005115)
Ruby의 멀티스레딩은 스레드가 IO 블록에 있을 때 대처할 수 있습니다. IO 블록 상태에서는 다른 스레드가 계속 실행될 수 있으므로 전체 처리 시간이 크게 단축됩니다.
위 코드 예제에서는 Ruby에서 Thread의 스레드 클래스를 사용합니다. Thread 클래스의 다중 스레드 프로그램을 작성하세요. Ruby 스레드는 코드에서 병렬 처리를 달성하는 확장 가능하고 효과적인 방법입니다. 다음으로 동시성 시나리오를 설명하겠습니다.
def thread_test time = Time.now threads = 3.times.map do Thread.new do sleep 3 end end puts "不用等3秒就可以看到我:#{Time.now - time}" threads.map(&:join) puts "现在需要等3秒才可以看到我:#{Time.now - time}" end test ## 不用等3秒就可以看到我:8.6e-05 ## 现在需要等3秒才可以看到我:3.003699
스레드 생성은 비차단입니다. 이렇게 하면 동시 동작을 시뮬레이션할 수 있습니다. 차단하는 경우 여러 스레드가 병렬성을 달성할 수 있습니다.
require 'benchmark' def multiple_threads count = 0 threads = 4.times.map do Thread.new do 2500000.times { count += 1} end end threads.map(&:join) end def single_threads time = Time.now count = 0 Thread.new do 10000000.times { count += 1} end.join end Benchmark.bm do |b| b.report { multiple_threads } b.report { single_threads } end ## user system total real ## 0.600000 0.010000 0.610000 ( 0.607230) ## 0.610000 0.000000 0.610000 ( 0.623237)여기에서 볼 수 있듯이 동일한 작업을 4개의 스레드가 병렬로 실행되는데 시간이 줄어들지 않는 이유는 무엇입니까?
글로벌락(GIL)이 존재하기 때문이죠! ! !
우리가 일반적으로 사용하는 루비는 GIL이라는 메커니즘을 사용합니다.
코드 병렬성을 달성하기 위해 멀티스레딩을 사용하더라도 이 전역 잠금이 있기 때문에 모든 time 하나의 스레드만 코드를 실행할 수 있으며, 실행할 수 있는 스레드는 기본 운영 체제의 구현에 따라 다릅니다.
CPU가 여러 개 있더라도 각 스레드 실행에 대해 몇 가지 추가 옵션만 제공합니다. 위 코드에서는 한 번에 하나의 스레드만 count += 1을 실행할 수 있습니다.
Ruby 멀티스레딩은 멀티코어 CPU를 재사용할 수 없습니다. 반대로 전체 시간은 단축되지 않습니다. 스레드 전환의 영향으로 인해 소요 시간도 약간 증가할 수 있습니다.
하지만 전에 자고 나니 분명히 평행성이 달성됐네요!
이것은 Ruby의 고급 설계입니다. 파일 읽기 및 쓰기, 네트워크 요청을 포함한 모든 차단 작업을 병렬화할 수 있습니다.
require 'benchmark' require 'net/http' # 模拟网络请求 def multiple_threads uri = URI("http://www.baidu.com") threads = 4.times.map do Thread.new do 25.times { Net::HTTP.get(uri) } end end threads.map(&:join) end def single_threads uri = URI("http://www.baidu.com") Thread.new do 100.times { Net::HTTP.get(uri) } end.join end Benchmark.bm do |b| b.report { multiple_threads } b.report { single_threads } end user system total real 0.240000 0.110000 0.350000 ( 3.659640) 0.270000 0.120000 0.390000 ( 14.167703)
프로그램은 네트워크 요청 차단 중에 발생하며 이러한 차단은 다음에서 병렬로 실행될 수 있습니다. Ruby이므로 시간이 많이 단축됩니다.
GIL에 대한 생각
이 GIL 잠금이 존재한다는 것은 우리 코드가 스레드로부터 안전하다는 뜻인가요?
那么, GIL 在 ruby代码的执行中什么时候会切换到另外一个线程去工作呢?
有几个明确的工作点:
方法的调用和方法的返回, 在这两个地方都会检查一下当前线程的gil的锁是否超时,是否要调度到另外线程去工作
所有io相关的操作, 也会释放gil的锁让其它线程来工作
在c扩展的代码中手动释放gil的锁
还有一个比较难理解, 就是ruby stack 进入 c stack的时候也会触发gil的检测
一个例子
@a = 1 r = [] 10.times do |e| Thread.new { @c = 1 @c += @a r << [e, @c] } end r ## [[3, 2], [1, 2], [2, 2], [0, 2], [5, 2], [6, 2], [7, 2], [8, 2], [9, 2], [4, 2]]
上述中r 里 虽然e的前后顺序不一样, 但是@c的值始终保持为 2 ,即每个线程时都能保留好当前的 @c 的值.没有线程简的调度.
如果在上述代码线程中加入 可能会触发GIL的操作 例如 puts 打印到屏幕:
@a = 1 r = [] 10.times do |e| Thread.new { @c = 1 puts @c @c += @a r << [e, @c] } end r ## [[2, 2], [0, 2], [4, 3], [5, 4], [7, 5], [9, 6], [1, 7], [3, 8], [6, 9], [8, 10]]
这个就会触发GIL的lock, 数据异常了.
小结
Web 应用大多是 IO 密集型的,利用 Ruby 多进程+多线程模型将能大幅提升系统吞吐量.其原因在于:当Ruby 某个线程处于 IO Block 状态时,其它的线程还可以继续执行,从而降低 IO Block 对整体的影响.但由于存在 Ruby GIL (Global Interpreter Lock),MRI Ruby 并不能真正利用多线程进行并行计算.
PS. 据说 JRuby 去除了GIL,是真正意义的多线程,既能应付 IO Block,也能充分利用多核 CPU 加快整体运算速度,有计划了解一些.
위 내용은 Ruby의 동시성, 병렬성 및 전역 잠금 코드 공유에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!