English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Ruby Multithreading

Each program running on the system is a process. Each process contains one or more threads.

A thread is a single sequential control flow in a program. Running multiple threads simultaneously in a single program to complete different tasks is called multi-threading.

In Ruby, we can create multi-threading through the Thread class. Ruby's threads are lightweight and can be used to implement parallel code efficiently.

Create Ruby thread

To start a new thread, simply call Thread.new:

# Thread #1 Code section
Thread.new {
  # Thread #2 Execute code
}
# Thread #1 Execute code

Online Example

The following example demonstrates how to use multi-threading in a Ruby program:

Online Example

#!/usr/bin/ruby
 
def func1
   i=0
   while i<=2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i=i+1
   end
end
 
def func2
   j=0
   while j<=2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j=j+1
   end
end
 
puts "Started At #{Time.now}"
t1=Thread.new{func1()
t2=Thread.new{func2()
t1.join
t2.join
puts "End at #{Time.now}"

The execution result of the above code is:

Started At Wed May 14 08:21:54 -0700 2014
func1 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:55 -0700 2014
func1 at: Wed May 14 08:21:56 -0700 2014
func2 at: Wed May 14 08:21:56 -0700 2014
func1 at: Wed May 14 08:21:58 -0700 2014
End at Wed May 14 08:22:00 -0700 2014

Thread Life Cycle

1The creation of a thread can use Thread.new, and it can also use Thread.start or Thread.fork to create a thread with the same syntax.

2After creating a thread, there is no need to start it, the thread will execute automatically.

3The Thread class defines some methods to control threads. The thread executes the code block in Thread.new.

4The last statement in the thread code block is the value of the thread, which can be called through the thread's methods. If the thread has completed execution, then the thread value is returned, otherwise no value is returned until the thread is completed.

5By using the Thread.current method, it returns an object representing the current thread. The Thread.main method returns the main thread.

6By using the Thread.Join method to execute the thread, this method will suspend the main thread until the current thread is completed.

thread status

There are5There are

thread statusreturn value
executablerun
sleepSleeping
exitaborting
Normal terminationfalse
Exception terminationnil

Thread and Exception

When a thread occurs an exception and is not caught by rescue, the thread is usually terminated without warning. However, if other threads are waiting for this thread due to Thread#join, then the waiting threads will also be triggered by the same exception.

begin
  t = Thread.new do
    Thread.pass  # The main thread is indeed waiting for join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

Use the following3This method can cause the interpreter to interrupt its execution when a thread terminates due to an exception.

  • Specify when starting the script.-dOption, and run in debug mode.

  • Using Thread.abort_on_exception to set the flag.

  • Use Thread#abort_on_exception to set the flag for the specified thread.

When using the above3After using this method, the entire interpreter will be interrupted.

t = Thread.new { ... }
t.abort_on_exception = true

Thread Synchronization Control

In Ruby, there are three ways to implement synchronization, namely:

1Through the Mutex class to implement thread synchronization

2. The Queue class that regulates data transfer implements thread synchronization

3. Use ConditionVariable to implement synchronization control

Thread synchronization is implemented through the Mutex class

Thread synchronization control is implemented through the Mutex class. If a program variable is needed simultaneously by multiple threads, part of this variable can be locked using lock. The code is as follows:

Online Example

#!/usr/bin/ruby
 
require "thread"
puts "Synchronize Thread"
 
@num=200
@mutex=Mutex.new
 
def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "You have successfully bought #{num} tickets"
          else
               puts "Sorry, not enough tickets"
          end
     @mutex.unlock
end
 
ticket1=Thread.new 10 do
     10.times do |value|
     ticketNum =15
     buyTicket(ticketNum)
     sleep 0.01
     end
end
 
ticket2=Thread.new 10 do
     10.times do |value|
     ticketNum =20
     buyTicket(ticketNum)
     sleep 0.01
     end
end
 
sleep 1
ticket1.join
ticket2.join

The execution result of the above code is:

Synchronize Thread
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets
Sorry, not enough tickets

In addition to using lock to lock variables, try_lock can also be used to lock variables, and Mutex.synchronize can be used to synchronize access to a variable.

The Queue class that regulates data transfer implements thread synchronization

The Queue class represents a thread-safe queue that can synchronize access to the end of the queue. Different threads can use the same instance of the class, but there is no need to worry about the synchronization of the data in the queue. Additionally, using the SizedQueue class can limit the length of the queue.

The SizedQueue class can greatly facilitate the development of thread-synchronized applications, as it is not necessary to worry about thread synchronization as long as items are added to this queue.

The classic producer-consumer problem:

Online Example

#!/usr/bin/ruby
 
require "thread"
puts "SizedQueue Test"
 
queue = Queue.new
 
producer = Thread.new do
     10.times do |i|
          sleep rand(i) # Let the thread sleep for a while
          queue << i
          puts "#{i} produced"
     end
end
 
consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     end
end
 
consumer.join

Program output:

SizedQuee Test
0 produced
1 produced
consumed 0
2 produced
consumed 1
consumed 2
3 produced
consumed 34 produced
consumed 4
5 produced
consumed 5
6 produced
consumed 6
7 produced
consumed 7
8 produced
9 produced
consumed 8
consumed 9

Thread Variables

Threads can have their private variables, which are written to the thread when the thread is created. They can be used within the scope of the thread but cannot be shared outside the thread.

But what if the local variables of a thread need to be accessed by other threads or the main thread? Ruby provides a way to create thread variables by name, similar to treating threads as hash-style hash tables. Data can be written using []= and read using [].

Online Example

#!/usr/bin/ruby
 
count = 0
arr = []
 
10.times do |i|
   arr[i] = Thread.new {
      sleep(rand(0)/10.0)
      Thread.current["mycount"] = count
      count += 1
   }
end
 
arr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"

The output result of the above code is:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

The main thread waits for the subthread to complete and then outputs each value separately. .

Thread Priority

The priority of a thread is a major factor affecting thread scheduling. Other factors include the length of time a thread occupies the CPU, thread grouping scheduling, etc.

You can get the priority of a thread using the Thread.priority method and adjust the priority using Thread.priority=.

The default priority of a thread is 0. Threads with higher priority should execute faster.

A thread can access all the data within its scope, but what should you do if you need to access data from another thread within a certain thread? The Thread class provides methods for threads to access each other's data, and you can simply treat a thread as a hash table, where you can write data using []= within any thread and read data using [].

athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop }
bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop }
cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}"}

As can be seen, by treating threads as a hash table and using the [] and []= methods, we have achieved data sharing between threads.

Thread mutual exclusion

Mutex (Mutual Exclusion = mutual exclusion lock) is a mechanism used in multithreaded programming to prevent two threads from reading and writing the same public resource (such as global variables) at the same time.

An example of not using mutex

Online Example

#!/usr/bin/ruby
require 'thread'
 
count1 =2 =
difference = 0
counter = Thread.new do
   loop do
      count1 += 1
      count2 += 1
   end
end
spy = Thread.new do
   loop do
      difference += (count1 - count2).abs
   end
end
sleep 1
puts "count"1 : #{count1"
puts "count"2 : #{count2"
puts "difference : #{difference}"

The following is the output result of the example run:

count1 :  9712487
count2 :  12501239
difference : 0

An example of using mutex

Online Example

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
count1 =2 =
difference = 0
counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 += 1
         count2 += 1
      end
    end
end
spy = Thread.new do
   loop do
       mutex.synchronize do
          difference += (count1 - count2).abs
       end
   end
end
sleep 1
mutex.lock
puts "count"1 : #{count1"
puts "count"2 : #{count2"
puts "difference : #{difference}"

The following is the output result of the example run:

count1 :  1336406
count2 :  1336406
difference : 0

Deadlock

Two or more computational units are waiting for each other to stop running to obtain system resources, but no one exits in advance, this situation is called deadlock.

For example, a process p1Occupied the display and must also use the printer, while the printer is occupied by process p2Occupation, p2And it must use the display, thus forming a deadlock.

When using Mutex objects, we need to be aware of thread deadlock.

Online Example

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: I have critical section, but will wait for cv"
      cv.wait(mutex)
      puts "A: I have critical section again! I rule!"
   }
}
 
puts "(Later, back at the ranch...)"
 
b = Thread.new {
   mutex.synchronize {
      puts "B: Now I am critical, but am done with cv"
      cv.signal
      puts "B: I am still critical, finishing up"
   }
}
a.join
b.join

The output result of the above example is as follows:

A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

Thread class methods

The complete Thread (thread) class methods are as follows:

Serial NumberMethod Description
1Thread.abort_on_exception
If its value is true, once a thread terminates due to an exception, the entire interpreter will be interrupted. Its default value is false, which means that in normal circumstances, if a thread occurs an exception and the exception is not detected by Thread#join or other detection, the thread will be terminated without warning.
2Thread.abort_on_exception=
If set to trueOnce a thread terminates due to an exception, the entire interpreter will be interrupted. Return the new status
3Thread.critical
Return a boolean value.
4Thread.critical=
When its value is true, thread switching will not occur. If the current thread is suspended (stop) or a signal (signal) intervenes, its value will automatically become false.
5Thread.current
Return the currently running thread (current thread).
6Thread.exit
Terminate the execution of the current thread and return the current thread. If the current thread is the only one, it will use exit(0) to terminate its execution.
7Thread.fork { block }
Generate a thread like Thread.new.
8Thread.kill( aThread )
Terminate the execution of the thread.
9Thread.list
Return an array of live threads that are in a running or suspended state.
10Thread.main
Return to the main thread.
11Thread.new( [ arg ]* ) {| args | block }
Generates a thread and starts executing. Values will be passed to the block unchanged. This allows values to be passed to the local variables inherent to the thread when starting the thread.
12Thread.pass
Pass the execution right to another thread. It will not change the state of the running thread, but will hand over control to another runnable thread (explicit thread scheduling).
13Thread.start( [ args ])* ) {| args | block }
Generates a thread and starts executing. Values will be passed to the block unchanged. This allows values to be passed to the local variables inherent to the thread when starting the thread.
14Thread.stop
Suspends the current thread until the thread using the run method wakes it up again.

Thread Example Method

The following example calls the thread example method join:

Online Example

#!/usr/bin/ruby
 
thr = Thread.new do   # Example
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # Call the example method join

The following is a complete list of example methods:

Serial NumberMethod Description
1thr[ name ]
Retrieve the inherent data corresponding to the name within the thread. Name can be a string or symbol. If there is no data corresponding to the name, return nil.
2thr[ name ]=
Set the value of the inherent data corresponding to the name within the thread, where name can be a string or symbol. If set to nil, the corresponding data within the thread will be deleted.
3thr.abort_on_exception
Return a boolean value.
4thr.abort_on_exception=
If the value is true, the entire interpreter will be interrupted once a thread terminates due to an exception.
5thr.alive?
If the thread is 'alive', return true.
6thr.exit
Terminate the thread's execution. Return self.
7thr.join
Suspend the current thread until the self thread stops running. If self terminates due to an exception, the same exception will be raised in the current thread.
8thr.key?
If the inherent data of the thread corresponding to the name has been defined, return true
9thr.kill
Similar to Thread.exit .
10thr.priority
Return the thread's priority. The default value of priority is 0. The higher the value, the higher the priority.
11thr.priority=
Set the thread's priority. It can also be set to a negative number.
12thr.raise( anException )
Forcefully raise an exception within this thread.
13thr.run
Restart the suspended (stop) thread. Unlike wakeup, it will immediately switch to the thread. If this method is used on a dead process, it will raise a ThreadError exception.
14thr.safe_level
Returns the safety level of self. The safe_level of the current thread is the same as $SAFE.
15thr.status
Uses the strings "run", "sleep", or "aborting" to represent the state of a live thread. If a thread terminates normally, it returns false. If it terminates due to an exception, it returns nil.
16thr.stop?
Returns true if the thread is in a terminated (dead) or suspended (stop) state.
17thr.value
Waits until the self thread terminates (equivalent to join) and returns the return value of the block of the thread. If an exception occurs during the execution of the thread, the exception will be raised again.
18thr.wakeup
Changes the state of a suspended (stop) thread to an executable (run) state. If this method is called on a dead thread, a ThreadError exception will be raised.