Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Ruby multithreaded


May 12, 2021 Ruby


Table of contents


Ruby multithreaded

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

A thread is a single sequential control process in a program that runs multiple threads at the same time to do different work, called multithreading.

In Ruby, we can create multithreaded threads through the Thread class, and Ruby's threads are lightweight and can implement parallel code in an efficient way.


Create a Ruby thread

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

# 线程 #1 代码部分
Thread.new {
  # 线程 #2 执行代码
}
# 线程 #1 执行代码

Instance

The following example shows how to use multithreaded in a Ruby program:

#!/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}"

Try it out . . .


The above code execution results are:

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 lifecycle

1, thread creation can use Thread.new, the same syntax can use Thread.start or Thread.fork three methods to create threads.

2, after creating a thread does not need to start, the thread will be executed automatically.

3, thread class defines some ways to manipulate threads. The thread executes the block of code in Thread.new.

4. The last statement in the thread code block is the value of the thread, which can be called by the thread's method, and if the thread is executed, the thread value is returned, otherwise the value is not returned until the thread is executed.

5, the Thread.current method returns an object that represents the current thread. The Thread.main method returns the main thread.

6, through the Thread.Join method to execute the thread, this method will suspend the main thread until the current thread is finished.


The thread state

Threads have five states:

The thread state Returns a value
Runnable run
Sleeping Sleeping
Aborting aborting
Terminated normally false
Terminated with exception nil

Threads and exceptions

When a thread has an exception and is not caught by reescue, the thread is usually terminated without warning. H owever, if there are other threads that have been waiting for the thread because of thread-join, the waiting thread will also throw the same exception.

begin
  t = Thread.new do
    Thread.pass    # 主线程确实在等join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

Using the following three methods, you can have the interpreter interrupt when a thread terminates due to an exception.

  • Specify the -d option when you start the script and run it when you debug the mold.
  • Use Thread.abort_on_exception set the flag.
  • Use Thread#abort_on_exception the specified thread.

When one of these three methods is used, the entire interpreter is interrupted.

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

Thread synchronization control

In Ruby, there are three ways to synchronize:

1. Thread synchronization through the Mutex class

2. The Queue class, which oversees data handover, implements thread synchronization

3. Use ConditionVariable for synchronization control

Thread synchronization is achieved through the Mutex class

Thread synchronization control is implemented through the Mutex class, which can be partially locked with lock if a program variable is required for multiple thread clocks at the same time. The code is as follows:

#encoding:gbk
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,no 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 output is as follows:

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,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets

In addition to locking variables, you can try_lock lock variables, and you can use Mutex.sync to synchronize access to a variable.

The Queue class, which oversees data handover, enables thread synchronization

A Queue class is a queue that supports threads and can access the end of the queue in sync. D ifferent threads can use a unified pair of classes, but don't worry about whether the data in this queue can be synchronized, and using the SedQueue class can limit the length of the queue

The SizedQueue class can be very convenient to help us develop thread synchronization applications, so that as long as we join this queue, we don't have to worry about thread synchronization.

Classic producer consumer issues:

#!/usr/bin/ruby

require "thread"
puts "SizedQuee Test"

queue = Queue.new

producer = Thread.new do
     10.times do |i|
          sleep rand(i) # 让线程睡眠一段时间
          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

Try it out . . .


The output of the program:

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

The thread variable

A thread can have its own private variable, which is written to the thread when it is created. Can be used within the scope of a thread, but cannot be shared outside the thread.

But sometimes, what if a thread's local variables need to be accessed by another thread or main thread? R uby provides a hash list that allows thread variables to be created by name, similar to the hash-style hash. T he data is written and read out by . Let's take a look at the following code:

#!/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 above code runs the output as:

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

The main thread waits for the child thread to complete execution, and then outputs each value separately.


Thread priority

The priority of a thread is a major factor affecting the scheduling of a thread. Other factors include the length of CPU execution, thread group scheduling, and so on.

You can use the Thread.priority method to get the thread priority and the Thread.priorityy method to prioritize the thread.

The thread's priority defaults to 0. Higher priority execution is faster.

A Thread can access all the data within its own scope, but what if there is a need to access data from other threads within one thread? The Thread class provides a way for thread data to access each other, so you can simply use a thread as a Hash table, you can write data in any thread, and you can read out 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 you can see, using the thread as a Hash table, we implemented data sharing between threads using the methods of . . . and . .


Threads are mutually exclusive

Mutal Exclusion is a mechanism used in multithreaded programming to prevent two threads from reading and writing on the same common resource, such as global variables, at the same time.

An instance of Mutax is not used

#!/usr/bin/ruby
require 'thread'

count1 = count2 = 0
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 "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

The above instance runs the output as:

count1 :  9712487
count2 :  12501239
difference : 0

Use an instance of Mutax

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

count1 = count2 = 0
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 "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

The above instance runs the output as:

count1 :  1336406
count2 :  1336406
difference : 0

Deadlock

More than two computing units, both of which are waiting for each other to stop running to obtain system resources, but none of them exit early, a condition known as a deadlock.

For example, a process p1 occupies the monitor while the printer must be used, while the printer is occupied by process p2, and p2 must use the monitor, creating a deadlock.

When we use Mutex objects, we need to be aware of thread deadlocks.

Instance

#!/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 of the above examples is:

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 method

The complete Thread class approach is as follows:

Serial number Method description
1 Thread.abort_on_exception
If its value is true, the entire interpreter is interrupted once a thread is terminated due to an exception. Its default value is false, that is, in general, if a thread has an exception and the exception is not detected by Thread-join, etc., the thread is terminated without warning.
2 Thread.abort_on_exception=
If set to true, the entire interpreter is interrupted once a thread is terminated due to an exception. Returns a new state
3 Thread.critical
Returns the Boolean value.
4 Thread.critical=
When its value is true, no thread switching is performed. If the current thread hangs or signals intervention, its value automatically changes to false.
5 Thread.current
Returns the thread (the current thread) that is currently running.
6 Thread.exit
Terminates the current thread. R eturns the current thread. If the current thread is the only thread, exit(0) is used to terminate it.
7 Thread.fork { block }
Build threads like Thread.new.
8 Thread.kill( aThread )
Terminate the thread.
9 Thread.list
Returns an array of live threads that are running or suspended.
10 Thread.main
Returns the main thread.
11 Thread.new( [ arg ]* ) {| args | block }
Build the thread and start executing. T he number is passed to the block intact. This allows the value to be passed to the local variables inherent in the thread while the thread is starting.
12 Thread.pass
Give run rights to other threads. It does not change the state of the running thread, but instead hands control to other runnable threads (explicit thread scheduling).
13 Thread.start( [ args ]* ) {| args | block }
Build the thread and start executing. T he number is passed to the block intact. This allows the value to be passed to the local variables inherent in the thread while the thread is starting.
14 Thread.stop
Suspend the current thread until other threads wake it up again using the run method.

Thread instantiation method

The following instance calls the thread instantiation method join:

#!/usr/bin/ruby

thr = Thread.new do   # 实例化
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # 调用实例化方法 join

The following is a complete list of instantiation methods:

Serial number Method description
1 thr[ name ]
Remove the inherent data within the thread that corresponds to the name. N ame can be a string or symbol. If there is no data corresponding to name, returns nil.
2 thr[ name ] =
Set the value of the intrinsic data that corresponds to the name in the thread, which can be a string or symbol. If set to nil, deletes the corresponding data within the thread.
3 thr.abort_on_exception
Returns the Boolean value.
4 thr.abort_on_exception=
If its value is true, the entire interpreter is interrupted once a thread is terminated due to an exception.
5 thr.alive?
If the thread is "live," return true.
6 thr.exit
The thread is terminated. Return to self.
7 thr.join
Suspend the current thread until the self thread terminates running. If self terminates due to an exception, throws the same exception on the current thread.
8 thr.key?
If the thread-inherent data corresponding to name has been defined, true is returned
9 thr.kill
Similar to Thread.exit.
10 thr.priority
Returns the thread's priority. T he default value for priority is 0. The higher the value, the higher the priority.
11 thr.priority=
Set the priority of the thread. It can also be set to negative.
12 thr.raise( anException )
An exception is forced within the thread.
13 thr.run
Restart the thread that is suspended (stop). U nlike wakeup, it will immediately switch threads. If the method is used for a dead process, threadError exception is thrown.
14 thr.safe_level
Return the security level of self. The current thread safe_level the same as $SAFE.
15 thr.status
Use the string "run," "sleep," or "aborting" to represent the state of a live thread. I f a thread terminates normally, it returns to false. If terminated due to an abnormality, return to niil.
16 thr.stop?
If the thread is in a terminated state (dead) or suspended (stop), return true.
17 thr.value
Wait until the self thread terminates the run (equivalent to the joy) and returns the return value of the thread's block. If an exception occurs while the thread is running, it is thrown again.
18 thr.wakeup
Changing the state of the thread that is suspended to executable (run) throws a ThreadError exception if the method is executed on a dead thread.