Home >Backend Development >Python Tutorial >Detailed explanation of concurrency, parallelism and global lock code sharing in ruby

Detailed explanation of concurrency, parallelism and global lock code sharing in ruby

巴扎黑
巴扎黑Original
2017-09-13 09:44:481676browse

I am learning ruby ​​recently and want to summarize and share what I have learned. The following article mainly introduces you to the relevant information about concurrency and global locks in ruby. The article introduces it in detail through example code. Friends in need can refer to it, let’s take a look below.

Preface

This article mainly introduces the relevant content about ruby ​​concurrency and global locks, and shares it for your reference and study. Below Not much to say, let’s take a look at the detailed introduction.

Concurrency and parallelism

When developing, we often come into contact with two concepts: concurrency and parallelism, almost all talk about Articles about concurrency and parallelism will mention one thing: Concurrency does not equal parallelism. So how to understand this sentence?

  • Concurrency: The chef received the menus ordered by 2 guests at the same time. Need to be processed.

  • Sequential execution: If there is only one chef, then he can only complete one menu after another.

  • Parallel execution : If there are two chefs, then they can cook in parallel and two people cook together.

Extend this example to our web development, you can understand it like this:

  • Concurrency: The server received requests initiated by two clients at the same time.

  • Sequential execution: The server has only one process (thread) to process the request and completes the first One request can complete the second request, so the second request needs to wait.

  • Parallel execution: The server has two processes (threads) to process the request, and both requests can Get a response without any issue of sequence.

According to the example described above, how can we simulate such a concurrent behavior in ruby? Look at the following code:

1. Sequential execution:

Simulate the operation when there is only one thread.


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)

The above code is very simple. Use sleep to simulate time-consuming operations. The time consumed during sequential execution.

2. Parallel execution

Simulate multiple Operations in threads


# 接上述代码
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)

We found that the time consuming under multi-threading is similar to that of f1, which is as expected. Parallelism can be achieved using multi-threading. .

Ruby's multi-threading can cope with IO Block. When a thread is in the IO Block state, other threads can continue to execute, thus greatly shortening the overall processing time.

Threads in Ruby

#The above code example uses the Thread class in Ruby. Ruby can easily write many Thread classes. Thread program. Ruby threads are a lightweight and effective way to achieve parallelism in your code.

The following describes a concurrency scenario


 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

The creation of Thread is non-blocking, so text can be output immediately. This simulates a concurrent behavior. Each thread sleeps for 3 seconds. In the case of blocking, multiple threads can achieve parallelism .

So at this time have we completed the parallel capability?

Unfortunately, my above description only mentioned that we are in non- Parallelism can be simulated in the case of blocking. Let's look at other examples:


require &#39;benchmark&#39;
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)

As can be seen from here, even if we divide the same task into 4 The threads are parallel, but the time is not reduced. Why is this?

Because there is a global lock (GIL)! ! !

Global lock

The ruby ​​we usually use uses a mechanism called GIL.

Even if we want to use multiple threads to achieve code parallelism, due to the existence of this global lock, only one thread can execute the code at a time. As for which thread can execute, this depends on the implementation of the underlying operating system.

Even if we have multiple CPUs, it only provides a few more options for the execution of each thread.

In our above code, only one thread can execute count += 1 at a time.

Ruby multi-threading cannot reuse multi-core CPUs. The overall time spent is not shortened after using multi-threading. On the contrary, due to the impact of thread switching, the time spent may increase slightly.

But when we slept before, we obviously achieved parallelism!

This is the advanced design of Ruby - all blocking operations can be parallelized, including reading and writing files, and network requests.


require &#39;benchmark&#39;
require &#39;net/http&#39;

# 模拟网络请求
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)

The program is blocked during network requests, and these blockings can be parallelized when running in Ruby, so the time consumption is greatly shortened.

Thoughts on GIL

So, since the existence of this GIL lock, does it mean that our code is thread-safe?

Unfortunately, no, GIL will switch to another worker thread at certain work points during Ruby execution. If some class variables are shared, it may cause trouble.

那么, 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 加快整体运算速度,有计划了解一些.

The above is the detailed content of Detailed explanation of concurrency, parallelism and global lock code sharing in ruby. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn