YouTip LogoYouTip

Ruby Multithreading

Every program running on a system is a process. Each process contains one or more threads. A thread is a single sequential control flow within a program. Running multiple threads simultaneously within a single program to perform different tasks is called multithreading. In Ruby, we can create multithreading through the Thread class. Ruby's threads are lightweight and can implement parallel code efficiently. * * * ## Creating Ruby Threads To start a new thread, you simply call Thread.new: Thread.new { } ### Example The following example demonstrates how to use multithreading in a Ruby program: ## Example 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=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 [Try it Β»](#) The output 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,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 using lock to lock variables, you can also use try_lock to lock variables, or use Mutex.synchronize to synchronize access to a variable. ### Thread Synchronization Using the Queue Class for Supervising Data Handover The Queue class represents a thread-safe queue that can synchronize access to the end of the queue. Different threads can use the same queue class without worrying about whether the data in the queue is synchronized. Additionally, using the SizedQueue class can limit the length of the queue. The SizedQueue class makes it very convenient for us to develop thread-synchronized applications because once you add to this queue, you don't need to worry about thread synchronization issues. Classic Producer-Consumer Problem: ## Example 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 Β»](#) The program output: SizedQuee Test0 produced 1 produced consumed 02 produced consumed 1 consumed 23 produced consumed 34 produced consumed 45 produced consumed 56 produced consumed 67 produced consumed 78 produced 9 produced consumed 8 consumed 9 * * * ## Thread Variables Threads can have their own private variables. A thread's private variables are written to the thread when it is created. They can be used within the thread's scope but cannot be shared outside the thread. However, what if a thread's local variables need to be accessed by other threads or the main thread? Ruby provides a way to create thread variables by name, treating the thread like a hash table. Data is written using []= and read using []. Let's look at the following code: ## Example count = 0 arr = []10.times do |i| arr = 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 of running the above code is: 8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10 The main thread waits for the child threads to finish executing, then outputs each value respectively. * * * ## Thread Priority Thread priority is the main factor affecting thread scheduling. Other factors include the length of CPU execution time, thread group scheduling, etc. You can use the Thread.priority method to get the thread's priority and the Thread.priority= method to adjust the thread's priority. The default thread priority is 0. Higher priority threads execute faster. A Thread can access all data within its own scope, but what 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. You can simply treat a thread as a Hash table, using []= to write data and [] to read data from any thread. 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, by treating the thread as a Hash table and using the [] and []= methods, we achieved data sharing between threads. * * * ## Thread Mutual Exclusion Mutex (Mutual Exclusion) is a mechanism used in multithreaded programming to prevent two threads from simultaneously reading and writing to the same public resource (such as a global variable). ### Example Without Using Mutex ## Example 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 output of running the above example is: count1 : 9712487 count2 : 12501239 difference : 0 ### Example Using Mutex ## Example 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 output of running the above example is: count1 : 1336406 count2 : 1336406 difference : 0 * * * ## Deadlock When two or more processing units are each waiting for the other to stop running in order to acquire system resources, but neither exits first, this situation is called a deadlock. For example, process p1 occupies the display and also needs to use the printer, but the printer is occupied by process p2, and p2 needs to use the display. This creates a deadlock. We need to be mindful of thread deadlock when using Mutex objects. ## Example 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 example 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 Methods The complete Thread class methods are as follows: | No. | Method Description | | --- | --- | | 1 | **Thread.abort_on_exception** If its value is true, the entire interpreter will be aborted when a thread terminates due to an exception. Its default value is false, meaning that under normal circumstances, if a thread raises an exception and that exception is not detected by Thread#join, etc., the thread will be terminated without warning. | | 2 | **Thread.abort_on_exception=** If set to _true_, the entire interpreter will be aborted when a thread terminates due to an exception. Returns the new state. | | 3 | **Thread.critical** Returns a boolean value. | | 4 | **Thread.critical=** When its value is true, thread switching will not occur. If the current thread is suspended (stop) or a signal intervenes, its value will automatically become false. | | 5 | **Thread.current** Returns the currently running thread (the current thread). | | 6 | **Thread.exit** Terminates the current thread's execution. Returns the current thread. If the current thread is the only thread, it will use exit(0) to terminate its execution. | | 7 | **Thread.fork { block }** Generates a thread, just like Thread.new. | | 8 | **Thread.kill( aThread )** Terminates the thread's execution. | | 9 | **Thread.list** Returns an array of live threads that are in a running or suspended state. | | 10 | **Thread.main** Returns the main thread. | | 11 | **Thread.new( * ) {| args | block }** Generates a thread and begins execution. Arguments are passed to the block unchanged. This allows values to be passed to the thread's inherent local variables when starting the thread. | | 12 | **Thread.pass** Passes execution to another thread. It does not change the state of the running thread but instead passes control to another runnable thread (explicit thread scheduling). | | 13 | **Thread.start( * ) {| args | block }** Generates a thread and begins execution. Arguments are passed to the block unchanged. This allows values to be passed to the thread's inherent local variables when starting the thread. | | 14 | **Thread.stop** Suspends the current thread until another thread uses the run method to wake it up again. | * * * ## Thread Instance Methods The following example calls the thread instance method join: ## Example thr = Thread.new do puts"In second thread"raise"Raise exception"end thr.join Here is the complete list of instance methods: | No. | Method Description | | --- | --- | | 1 | **thr** Retrieves the inherent data corresponding to name within the thread. name can be a string or symbol. If there is no data corresponding to name, returns nil. | | 2 | **thr =** Sets the value of the inherent data corresponding to name within the thread. name can be a string or symbol. If set to nil, it will delete the corresponding data within the thread. | | 3 | **thr.abort_on_exception** Returns a boolean value. | | 4 | **thr.abort_on_exception=** If its value is true, the entire interpreter will be aborted when a thread terminates due to an exception. | | 5 | **thr.alive?** Returns true if the thread is "alive". | | 6 | **thr.exit** Terminates the thread's execution. Returns self. | | 7 | **thr.join** Suspends the current thread until the self thread terminates execution. If self terminates due to an exception, it will raise the same exception in the current thread. | | 8 | **thr.key?** Returns true if the inherent data corresponding to name has been defined for the thread. | | 9 | **thr.kill** Similar to _Thread.exit_. | | 10 | **thr.priority** Returns the thread's priority. The default priority is 0. The higher the value, the higher the priority. | | 11 | **thr.priority=** Sets the thread's priority. It can also be set to a negative number. | | 12 | **thr.raise( anException )** Forces an exception to be raised within this thread. | | 13 | **thr.run** Restarts a suspended (stop) thread. Unlike wakeup, it will immediately perform a thread switch. If this method is used on a dead thread, it will raise a ThreadError exception. | | 14 | **thr.safe_level** Returns the safety level of self. The current thread's safe_level is the same as $SAFE. | | 15 | **thr.status** Represents the state of a live thread using the strings "run", "sleep", or "aborting". If a thread terminates normally, it returns false. If it terminates due to an exception, it returns nil. | | 16 | **thr.stop?** Returns true if the thread is in a terminated (dead) or suspended (stop) state. | | 17 | **thr.value** Waits until the self thread terminates execution (equivalent to join), then returns the return value of the thread's block. If an exception occurs during the thread's execution, it will re-raise that exception. | | 18 | **thr.wakeup** Changes the state of a suspended (stop) thread to executable (run). If this method is used on a dead thread, it will raise a ThreadError exception. |
← Python IdeRuby Web Services β†’