루비에서 스레드로부터 안전하지 않은 것이 무엇인지 아는 방법?
Rails 4 부터 모든 것이 기본적으로 스레드 환경에서 실행되어야합니다. 이것이 의미하는 것은 우리가 작성하는 모든 코드 와 우리가 사용하는 모든 gem은threadsafe
그래서 이것에 대해 몇 가지 질문이 있습니다.
- 루비 / 레일에서 스레드로부터 안전하지 않은 것은 무엇입니까? Vs 루비 / 레일에서 스레드로부터 안전한 것은 무엇입니까?
- 보석의 목록이 거기에 있다 스레드가 알려진 또는 그 반대는?
- threadsafe 예제가 아닌 코드의 일반적인 패턴 목록이
@result ||= some_method
있습니까? - 등의 루비 랭 코어의 데이터 구조는
Hash
스레드 안전합니까? - MRI에서를 제외하고 한 번에 하나의 루비 스레드 만 실행할 수 있음을 의미 하는
GVL
/GIL
IO
가있는 경우 스레드 세이프 변경이 우리에게 영향을 줍니까?
핵심 데이터 구조는 스레드로부터 안전하지 않습니다. Ruby와 함께 제공되는 유일한 것은 표준 라이브러리 ( require 'thread'; q = Queue.new
) 의 큐 구현입니다 .
MRI의 GIL은 스레드 안전 문제에서 우리를 구하지 않습니다. 두 개의 스레드가 동시에 Ruby 코드 를 실행할 수 없도록합니다 . 즉, 정확히 같은 시간에 서로 다른 두 개의 CPU에서 실행됩니다. 스레드는 코드의 어느 시점에서나 일시 중지 및 재개 될 수 있습니다. @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
예를 들어 여러 스레드에서 공유 변수를 변경하는 것과 같은 코드를 작성하면 나중에 공유 변수의 값이 결정적이지 않습니다. GIL은 다소간 단일 코어 시스템의 시뮬레이션이며 올바른 동시 프로그램 작성의 근본적인 문제를 변경하지 않습니다.
MRI가 Node.js와 같이 단일 스레드 된 경우에도 동시성에 대해 생각해야합니다. 증분 변수가있는 예제는 잘 작동하지만, 비 결정적 순서로 일이 발생하고 하나의 콜백이 다른 결과를 방해하는 경합 조건을 얻을 수 있습니다. 단일 스레드 비동기 시스템은 추론하기가 더 쉽지만 동시성 문제가 발생하지 않습니다. 여러 사용자가있는 응용 프로그램을 생각해보십시오. 두 명의 사용자가 거의 동시에 Stack Overflow 게시물에서 편집을 누르면 게시물을 편집하는 데 시간을 할애 한 다음 저장을 누르면 나중에 세 번째 사용자가 변경 사항을 볼 수 있습니다. 같은 게시물을 읽었습니까?
Ruby에서는 대부분의 다른 동시 런타임에서와 같이 둘 이상의 작업이 스레드로부터 안전하지 않습니다. @n += 1
다중 작업이기 때문에 스레드로부터 안전하지 않습니다. @n = 1
하나의 작업이기 때문에 스레드로부터 안전합니다 (내부에서 많은 작업이 수행되며 "스레드 안전"인 이유를 자세히 설명하려고하면 문제가 발생할 수 있지만 결국 할당에서 일관성없는 결과를 얻지 못할 것입니다. ). @n ||= 1
, 그렇지 않으며 다른 속기 연산 + 할당도 없습니다. 내가 여러 번 저지른 한 가지 실수 return unless @started; @started = true
는 쓰레드로부터 안전하지 않은을 쓰는 것입니다.
Ruby에 대한 스레드 안전 및 비 스레드 안전 명령문의 신뢰할 수있는 목록은 모르지만 간단한 경험 규칙이 있습니다. 표현식이 하나의 (부작용이없는) 작업 만 수행하면 스레드로부터 안전 할 수 있습니다. 예를 들어 : a + b
is ok, a = b
is also ok, and a.foo(b)
is ok, 만약 메소드 foo
가 부작용이 없다면 (루비의 거의 모든 것이 메소드 호출이고, 많은 경우 할당이기 때문에 이것은 다른 예제에도 적용됩니다). 이 맥락에서 부작용은 상태를 변경하는 것을 의미합니다. 부작용 def foo(x); @x = x; end
이 없습니다 .
Ruby에서 스레드 안전 코드를 작성할 때 가장 어려운 점 중 하나는 배열, 해시 및 문자열을 포함한 모든 핵심 데이터 구조가 변경 가능하다는 것입니다. 실수로 상태의 일부를 유출하는 것은 매우 쉽습니다. 그리고 그 부분이 변경 가능할 때 상황이 정말 망가질 수 있습니다. 다음 코드를 고려하십시오.
class Thing
attr_reader :stuff
def initialize(initial_stuff)
@stuff = initial_stuff
@state_lock = Mutex.new
end
def add(item)
@state_lock.synchronize do
@stuff << item
end
end
end
이 클래스의 인스턴스는 스레드간에 공유 될 수 있으며 안전하게 추가 할 수 있지만 동시성 버그가 있습니다 (유일한 것이 아님). 객체의 내부 상태가 stuff
접근 자를 통해 누출됩니다 . 캡슐화 관점에서 문제가되는 것 외에도 동시성 웜 캔을 엽니 다. 누군가 그 배열을 가져 와서 다른 곳으로 넘겨 주면, 그 코드는 이제 배열을 소유하고 원하는대로 할 수 있다고 생각합니다.
또 다른 고전적인 Ruby 예제는 다음과 같습니다.
STANDARD_OPTIONS = {:color => 'red', :count => 10}
def find_stuff
@some_service.load_things('stuff', STANDARD_OPTIONS)
end
find_stuff
works fine the first time it's used, but returns something else the second time. Why? The load_things
method happens to think it owns the options hash passed to it, and does color = options.delete(:color)
. Now the STANDARD_OPTIONS
constant doesn't have the same value anymore. Constants are only constant in what they reference, they do not guarantee the constancy of the data structures they refer to. Just think what would happen if this code was run concurrently.
If you avoid shared mutable state (e.g. instance variables in objects accessed by multiple threads, data structures like hashes and arrays accessed by multiple threads) thread safety isn't so hard. Try to minimize the parts of your application that are accessed concurrently, and focus your efforts there. IIRC, in a Rails application, a new controller object is created for every request, so it is only going to get used by a single thread, and the same goes for any model objects you create from that controller. However, Rails also encourages the use of global variables (User.find(...)
uses the global variable User
, you may think of it as only a class, and it is a class, but it is also a namespace for global variables), some of these are safe because they are read only, but sometimes you save things in these global variables because it is convenient. Be very careful when you use anything that is globally accessible.
It's been possible to run Rails in threaded environments for quite a while now, so without being a Rails expert I would still go so far as to say that you don't have to worry about thread safety when it comes to Rails itself. You can still create Rails applications that aren't thread safe by doing some of the things I mention above. When it comes other gems assume that they are not thread safe unless they say that they are, and if they say that they are assume that they are not, and look through their code (but just because you see that they go things like @n ||= 1
does not mean that they are not thread safe, that's a perfectly legitimate thing to do in the right context -- you should instead look for things like mutable state in global variables, how it handles mutable objects passed to its methods, and especially how it handles options hashes).
Finally, being thread unsafe is a transitive property. Anything that uses something that is not thread safe is itself not thread safe.
In addition to Theo's answer, I'd add a couple problem areas to lookout for in Rails specifically, if you're switching to config.threadsafe!
Class variables:
@@i_exist_across_threads
ENV:
ENV['DONT_CHANGE_ME']
Threads:
Thread.start
starting from Rails 4, everything would have to run in threaded environment by default
This is not 100% correct. Thread-safe Rails is just on by default. If you deploy on a multi-process app server like Passenger (community) or Unicorn there will be no difference at all. This change only concerns you, if you deploy on a multi-threaded environment like Puma or Passenger Enterprise > 4.0
In the past if you wanted to deploy on a multi-threaded app server you had to turn on config.threadsafe, which is default now, because all it did had either no effects or also applied to a Rails app running in a single process (Prooflink).
But if you do want all the Rails 4 streaming benefits and other real time stuff of the multi-threaded deployment then maybe you will find this article interesting. As @Theo sad, for a Rails app, you actually just have to omit mutating static state during a request. While this a simple practice to follow, unfortunately you cannot be sure about this for every gem you find. As far as i remember Charles Oliver Nutter from the JRuby project had some tips about it in this podcast.
And if you want to write a pure concurrent Ruby programming, where you would need some data structures which are accessed by more than one thread you maybe will find the thread_safe gem useful.
참고URL : https://stackoverflow.com/questions/15184338/how-to-know-what-is-not-thread-safe-in-ruby
'IT박스' 카테고리의 다른 글
싱글 톤이 나쁘면 서비스 컨테이너가 좋은 이유는 무엇입니까? (0) | 2020.09.08 |
---|---|
Mac에서 $ PATH에 / usr / local / bin을 추가하는 방법 (0) | 2020.09.08 |
신뢰할 수있는 UDP가 필요할 때 무엇을 사용합니까? (0) | 2020.09.08 |
테이블의 열을 논리적으로 재정렬 할 수 있습니까? (0) | 2020.09.08 |
단일 null 인수로 Java varargs 메서드를 호출합니까? (0) | 2020.09.08 |