A preliminary introduction to Ruby multithreaded programming

Time:2022-5-4

Traditional programs have a separate thread to execute, and the statements or instructions containing the program are executed sequentially until the program terminates.

A multithreaded program has the execution of multiple threads. Each thread is executed sequentially, but on a multi-core CPU machine, threads may execute in parallel. For example, generally, in a single CPU machine, multiple threads are not actually executed in parallel, but simulate the execution of parallel crossed threads.

RubyYou can easily write multithreaded programs using the thread class. Ruby thread is a lightweight and efficient way to achieve parallelism in code.
To create a ruby thread:

To start a new thread, associate a block by calling thread new。 A new thread will be created to execute the code block, and the original thread will be immediately removed from the thread New returns and continues with the next statement:

?
1
2
3
4
5
# Thread #1 is running here
Thread.new {
 # Thread #2 runs this code
}
# Thread #1 runs this code

For example:

Here is an example of how we can use multithreaded Ruby programs.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/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}"

This will produce the following results:

?
1
2
3
4
5
6
7
8
Started At Wed May 14 08:21:54 -0700 2008
func1 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:54 -0700 2008
func2 at: Wed May 14 08:21:55 -0700 2008
func1 at: Wed May 14 08:21:56 -0700 2008
func2 at: Wed May 14 08:21:56 -0700 2008
func1 at: Wed May 14 08:21:58 -0700 2008
End at Wed May 14 08:22:00 -0700 2008

Thread life cycle:

Create a new thread with thread new。 You can also use the synonym thread Start and thread fork。

There is no need to start a thread. After it is created, it will automatically start running when CPU resources become available.

The thread class defines some methods to query and process threads at run time. Run the code in a thread block to call thread New, and then it stops running.

The value of the last expression in the block is the value of the thread. You can call the method of thread object value. If the thread is completed, this value is the return value of the thread. Otherwise, the value method blocks and does not return until the thread has completed.
Class method thread Current returns the thread object representing the current thread. This allows threads to manipulate themselves. Class method thread Main returns the thread object, which represents the main thread, thread When this initial thread starts executing Ruby programs.

You can wait for a specific thread by calling the thread Join method. The calling thread will be blocked until the given thread completes.
Threads and exceptions:

If an exception is thrown in the main thread and is not handled anywhere, the ruby interpreter prints a message and exits. In threads other than the main thread, unhandled exceptions cause the thread to stop running.

If thread t exits because of an unhandled exception, and another thread calls t.join or t.value, the exception that occurs is raised in thread s in t.

If thread abort_ on_ Exception is false. By default, unhandled exceptions only kill the current thread and all the others to continue running.

If you want to explain the exit caused by any unhandled exception in any thread, set the class method thread abort_ on_ Exception is true.

?
1
2
t = Thread.new { ... }
t.abort_on_exception = true

Thread variables:

A thread can normally access any variable in the range when the thread is created. The local variables of a thread block are local to the thread, not shared.

The thread class provides a special function that allows you to create and access thread local variables by name. Just put the thread object, if it is a hash, write the elements using [] = and read them back using [].

In this example, each thread records the current value of the count variable and a ThreadLocal variable of the key MYCOUNT.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/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}"

This will produce the following results:

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

The main thread waits for the sub thread to complete, and then prints out the value of each captured count.
Thread priority:

The first factor affecting thread scheduling is thread priority: the low priority thread planned before the high priority thread. More specifically, a thread will only get CPU time if there is no higher priority thread waiting to run.

You can set and query the priority of a ruby thread object = and priority. The newly created thread starts creating it on threads of the same priority. The priority of starting the main thread is 0.

There is no way to set thread priority before starting running. However, a thread can raise or lower its priority for the first operation.
Thread exclusion:

If two threads share access to the same data and at least one thread modifies the data, you must be very careful to ensure that no thread can see that the data is in an inconsistent state. This is called thread exclusion.

Mutex class is a mutually exclusive access to some shared resources, which implements a simple signal locking. That is, only one thread can hold the lock at a given time. Other threads may choose to queue for locks to become available, or they can simply choose to get an error immediately, indicating that the lock is not available.

By placing all access to shared data under the control of mutexes, we ensure consistency and atomic operations. In our trial example, the first one does not need mutax, and the second one uses mutax:
Examples without mutax:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/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}"

This will produce the following results:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
count1 : 1583766
count2 : 1583766
difference : 637992
 
#!/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}"

This will produce the following results:

?
1
2
3
count1 : 696591
count2 : 696591
difference : 0

Handling deadlocks:

When we start using thread exclusion of mutex objects, we must be careful to avoid deadlocks. When a deadlock occurs, all threads are waiting to acquire resources held by another thread. Because all threads are blocked, they cannot release the lock they hold. Because they can not release locks, other threads cannot obtain these locks.

It is used to protect a resource within a specific scope and is only associated with a signal. When a resource is not available, wait for a condition variable. This action releases the corresponding mutex. When the resources of some other threads to send signals are available, the original thread waits and restores the lock critical area at the same time.
example:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/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

This will produce the following results:

?
1
2
3
4
5
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 status:

There are five possible return values corresponding to the five possible states shown in the table below. The thread state returned by this method.

A preliminary introduction to Ruby multithreaded programming
Method of thread class:

The thread class provides the following methods that apply to all threads of the program. These methods are called with the name of the thread class, as follows:

?
1
Thread.abort_on_exception = true

Here is a complete list of all class methods:

A preliminary introduction to Ruby multithreaded programming

A preliminary introduction to Ruby multithreaded programming

Thread instance method:

These methods are an instance applicable to a thread. These methods will be called, and an example of using a thread is as follows:

?
1
2
3
4
5
6
7
#!/usr/bin/ruby
 
thr = Thread.new do  # Calling a class method new
  puts "In second thread"
  raise "Raise exception"
end
thr.join  # Calling an instance method join

Here is a complete list of all instance methods:

A preliminary introduction to Ruby multithreaded programming

A preliminary introduction to Ruby multithreaded programming