Ruby multi-thread programming
A traditional program has a separate thread for execution. The statements or commands containing the program are executed sequentially until the program is terminated.
A multi-threaded program has multiple threads for execution. Each thread is executed in sequence, but the thread may be executed in parallel on a multi-core CPU machine. For example, in a single CPU machine, multiple threads are not executed in parallel, but are executed in parallel.
Ruby can use the Thread class to easily write multi-threaded programs. The Ruby thread is a lightweight and efficient method for implementing concurrency in code.
Create a Ruby thread:
To start a new Thread, associate a block by calling Thread. new. A new code block executed by the Thread will be created, and the original Thread will immediately return from Thread. new and continue to execute the next statement:
# Thread #1 is running hereThread.new { # Thread #2 runs this code}# Thread #1 runs this code
For example:
Here is an example of how we can use a multi-threaded Ruby program.
#!/usr/bin/rubydef func1 i=0 while i<=2 puts "func1 at: #{Time.now}" sleep(2) i=i+1 endenddef func2 j=0 while j<=2 puts "func2 at: #{Time.now}" sleep(1) j=j+1 endendputs "Started At #{Time.now}"t1=Thread.new{func1()}t2=Thread.new{func2()}t1.joint2.joinputs "End at #{Time.now}"
This produces the following results:
Started At Wed May 14 08:21:54 -0700 2008func1 at: Wed May 14 08:21:54 -0700 2008func2 at: Wed May 14 08:21:54 -0700 2008func2 at: Wed May 14 08:21:55 -0700 2008func1 at: Wed May 14 08:21:56 -0700 2008func2 at: Wed May 14 08:21:56 -0700 2008func1 at: Wed May 14 08:21:58 -0700 2008End at Wed May 14 08:22:00 -0700 2008
Thread lifecycle:
Create a new Thread using Thread. new. You can also use the synonyms Thread. Start and Thread. fork.
There is no need to start a thread. After it is created, it will automatically start running and CPU resources become available.
The Thread class defines some methods to query and process the threads at runtime. Run the code in a Thread block to call Thread. new, and then it stops running.
The value of the last expression in this block is the value of the Thread. You can call the method of the value of the Thread object. If the thread is completed, the value is the return value of the thread. Otherwise, the value method will block and will not return until the thread has completed.
Class Method Thread. current returns the Thread object of the current Thread. This allows the thread to manipulate itself. Class Method Thread. main the returned thread object represents the main Thread, and the initial thread Thread starts to execute the Ruby program.
Wait for a specific Thread to call the Thread. Join method of the Thread. The call thread is blocked until the specified thread is completed.
Thread and exception:
If an exception is thrown in the main thread, the Ruby interpreter prints a message and exits. In other threads other than the main thread, the thread stops running due to an unhandled exception.
If the thread t exits because of an unhandled exception and the other thread calls t. join or t. value, the exception occurs in the thread s proposed in t.
If Thread. abort_on_exception is false, the unhandled exception only kills the current Thread and all other tasks.
If you want to cause interpretation exit due to any unprocessed exceptions in any Thread, set the class method Thread. abort_on_exception to true.
t = Thread.new { ... }t.abort_on_exception = true
Thread variable:
A thread can normally access any variable in the range when the thread is created. The local variable of a thread block is the local variable of the thread, rather than sharing.
The Thread class provides a special function that allows you to create and access local variables of a Thread 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 counting variable and a threadlocal variable of the key mycount.
#!/usr/bin/rubycount = 0arr = []10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 }endarr.each {|t| t.join; print t["mycount"], ", " }puts "count = #{count}"
This will produce the following results:
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 the value of each capture count.
Thread priority:
The first factor affecting thread scheduling is the thread priority: the thread with a low priority planned before the high priority. More specifically, a thread will only get the CPU time if there is no higher priority thread waiting for running.
You can set and query the priority = and priority of a Ruby thread object. The newly created thread starts to create it in a thread with the same priority. The priority of the main thread to be started is 0.
There is no way to set the thread priority before running. However, a thread can increase 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 the data in an inconsistent state. This is called thread exclusion.
The Mutex class provides Mutex access to some shared resources and implements a simple signal lock. That is, only one thread can hold the lock at a given time. Other threads may choose to wait for the lock to become available, or you can simply choose to get an error immediately, indicating that the lock is unavailable.
By controlling all mutex that access Shared data, we ensure consistency and atomic operations. In our example, the first one does not require mutax, and the second one uses mutax:
Examples without Mutax:
#!/usr/bin/rubyrequire 'thread'count1 = count2 = 0difference = 0counter = Thread.new do loop do count1 += 1 count2 += 1 endendspy = Thread.new do loop do difference += (count1 - count2).abs endendsleep 1puts "count1 : #{count1}"puts "count2 : #{count2}"puts "difference : #{difference}"
This produces the following results:
count1 : 1583766count2 : 1583766difference : 637992#!/usr/bin/rubyrequire 'thread'mutex = Mutex.newcount1 = count2 = 0difference = 0counter = Thread.new do loop do mutex.synchronize do count1 += 1 count2 += 1 end endendspy = Thread.new do loop do mutex.synchronize do difference += (count1 - count2).abs end endendsleep 1mutex.lockputs "count1 : #{count1}"puts "count2 : #{count2}"puts "difference : #{difference}"
This produces the following results:
count1 : 696591count2 : 696591difference : 0
Handle deadlocks:
When we start to use the thread exclusion of mutex objects, we must be careful to avoid deadlocks. When a deadlock occurs, all threads are waiting to obtain the resources held by another thread. Because all threads are blocked, they cannot release the lock they hold. Because they can not release the locks, other threads cannot obtain these locks.
A condition variable is only a signal associated with resources and used within the protection range of a specific mutex lock. When a resource is required to be unavailable, wait for a condition variable. This action releases the corresponding mutex lock. When some other threads send signals to the resource is available, the original thread will wait and restore the lock critical zone at the same time.
Example:
#!/usr/bin/rubyrequire 'thread'mutex = Mutex.newcv = ConditionVariable.newa = 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.joinb.join
This produces the following results:
A: I have critical section, but will wait for cv(Later, back at the ranch...)B: Now I am critical, but am done with cvB: I am still critical, finishing upA: I have critical section again! I rule!
Thread status:
Five possible return values correspond to five possible states shown in the following table. The thread status returned by this status method.
Thread class method:
The Thread class provides the following methods, which apply to all threads of the program. These methods are called using the name of the Thread class, as shown below:
Thread.abort_on_exception = true
Here is a complete list of all class methods:
Thread instance method:
These methods are applicable to an instance of a thread. These methods will be called. An instance using a thread is as follows:
#!/usr/bin/rubythr = Thread.new do # Calling a class method new puts "In second thread" raise "Raise exception"endthr.join # Calling an instance method join
Here is a complete list of all instance methods: