May 12, 2021 Ruby
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.
To start a new thread, simply call Thread.new:
# 线程 #1 代码部分
Thread.new {
# 线程 #2 执行代码
}
# 线程 #1 执行代码
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}"
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
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.
Threads have five states:
The thread state | Returns a value |
---|---|
Runnable | run |
Sleeping | Sleeping |
Aborting | aborting |
Terminated normally | false |
Terminated with exception | nil |
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.
Thread.abort_on_exception
set the flag.
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
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 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.
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
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
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.
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 . .
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.
#!/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
#!/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
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.
#!/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!
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. |
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. |