ruby에서 병렬 및 전역 자물쇠 상세 정보
본고는 주로 루비의 병발 병행과 전역 자물쇠에 관한 내용을 소개하고 여러분의 참고 학습을 위해 공유합니다. 다음은 더 이상 할 말이 없습니다. 상세한 소개를 봅시다.
병렬
개발할 때 우리는 두 가지 개념을 자주 접할 수 있다. 병발과 병행이다. 거의 모든 병발과 병행에 관한 문장은 한 가지를 언급한다. 병발은 병발과 같지 않다.그렇다면 이 말을 어떻게 이해합니까?
1. 순서대로 실행:
시뮬레이션은 하나의 라인만 있을 때의 조작이다.
require 'benchmark'
def f1
puts "sleep 3 seconds in f1
"
sleep 3
end
def f2
puts "sleep 2 seconds in f2
"
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)
상술한 코드는 매우 간단해서sleep로 시간을 소모하는 조작을 시뮬레이션한다.순서대로 실행할 때의 소모 시간.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)
우리는 다중 노드에서 소모되는 시간과 f1의 소모 시간이 비슷하다는 것을 발견했다. 이것은 우리가 예상한 것과 마찬가지로 다중 노드를 사용하면 병행할 수 있다.루비의 다중 스레드는 IO Block에 대응할 수 있으며, 어떤 스레드가 IO Block 상태에 있을 때, 다른 스레드는 계속 실행할 수 있어 전체 처리 시간을 대폭 단축시킬 수 있다.
루비의 스레드
상기 코드 예시에서 루비의Thread 루틴 클래스를 사용했습니다. 루비는Thread 클래스의 다중 루틴 프로그램을 쉽게 쓸 수 있습니다.루비 라인은 당신의 코드의 병행을 실현하기 위해 경량급과 효과적인 방식이다.
이어서 병발할 때의 정경을 묘사하다
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
Thread 생성이 막히지 않으므로 텍스트를 즉시 출력할 수 있습니다.이렇게 해서 하나의 병행 행위를 모의하였다.각 스레드sleep 3초, 막힌 상황에서 다중 스레드는 병행할 수 있습니다.그러면 이쯤에서 저희가 병행 능력을 완성하지 않았을까요?
매우 유감스럽지만, 나의 상술한 묘사에서 단지 우리가 막히지 않는 상황에서 시뮬레이션을 병행할 수 있다는 것을 언급했을 뿐이다.다른 예를 살펴보겠습니다.
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)
여기서 알 수 있듯이 우리가 같은 임무를 네 개의 라인으로 나누어 병행하더라도 시간이 줄어들지 않는 것은 왜일까?글로벌 자물쇠(GIL)가 있으니까!!!
전역 잠금
우리가 일반적으로 사용하는 루비는 GIL이라고 불리는 메커니즘을 채택했다.
설령 우리가 다중 스레드를 사용하여 코드의 병행을 실현하기를 원한다 하더라도, 이 전역 자물쇠의 존재로 인해 매번 하나의 스레드만 코드를 실행할 수 있고, 어느 스레드가 실행될 수 있는지는 하부 운영체제의 실현에 달려 있다.
설령 우리가 여러 개의 CPU를 가지고 있다 하더라도, 모든 라인의 실행을 위해 몇 가지 선택을 더 제공할 뿐이다.
우리 위의 코드에는 매번 하나의 라인만 실행할 수 있습니다count + = 1.
Ruby 멀티스레드는 멀티 코어 CPU를 재사용할 수 없습니다. 멀티스레드를 사용한 후 전체적으로 소요되는 시간이 짧지 않고 오히려 스레드 전환의 영향으로 소요되는 시간이 약간 증가할 수 있습니다.
하지만 우리가 전에 sleep를 했을 때 분명히 병행을 이루었잖아!
이것이 바로 루비 디자인의 고급 부분이다. 모든 차단 조작은 병행할 수 있고, 파일 읽기, 네트워크 요청을 포함한 조작은 병행할 수 있다.
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)
네트워크가 요청할 때 프로그램이 막혔는데, 이러한 막힘은 루비의 운행하에서 병행할 수 있기 때문에 소모 시간이 크게 단축되었다.GIL의 사고
그렇다면 이 GIL 자물쇠가 존재한다는 것은 우리의 코드가 안전하다는 것을 의미하는 것입니까?
안타깝게도 GIL은 루비 실행 중 어떤 작업점이 있을 때 다른 작업 라인으로 전환됩니다. 어떤 변수를 공유할 때 구덩이를 밟을 수 있습니다.
그러면 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을 촉발합니다. 데이터가 이상합니다.작은 매듭
웹 응용 프로그램은 대부분 IO 밀집형이므로 루비 다중 프로세스 + 다중 스레드 모델을 이용하여 시스템의 토출량을 대폭 향상시킬 수 있다.그 이유는 루비의 어떤 스레드가 IO Block 상태에 있을 때 다른 스레드가 계속 실행되어 IO Block이 전체에 미치는 영향을 낮출 수 있기 때문이다.그러나 루비 GIL(Global Interpreter Lock)이 존재하기 때문에 MRI 루비는 다중 스레드를 이용하여 병렬 계산을 할 수 없다.
PS. JRuby는 GIL을 제거한 진정한 의미의 다중 스레드로 IO 블록에 대처할 수 있을 뿐만 아니라 멀티 코어 CPU를 충분히 이용하여 전체 연산 속도를 가속화할 수 있다고 합니다.
총결산
이상은 바로 이 글의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 대해 참고 학습 가치가 있기를 바랍니다. 의문이 있으면 댓글로 교류해 주십시오. 저희에 대한 지지에 감사드립니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Ruby에서 문자열을 고정하는 다른 방법Ruby에서 문자열을 고정하려면 최소한 두 가지 방법이 있습니다. 아시다시피 a와 b는 동일한 개체 인스턴스인 것 같습니다. 문자열에 적용할 수 있는 일종의 이상한 메서드 입력: - 다음과 같은 코드를 볼 수 있습니...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.