CSC/ECE 517 Fall 2013/ch1 1w13 aa

From Expertiza_Wiki
Jump to navigation Jump to search

Multithreading in Rails

Traditional programs have a single-thread of execution. The statements or instructions that comprise the program are executed sequentially until the program terminates. The threads are in-process and are implemented by the Ruby interpreter. As interpreted code is independent of the operating system, ruby threads are completely portable. On the other hand, there are some disadvantages when compared to native threads, such as deadlock and starvation. If a thread makes a call to the operating system that takes a long time to complete, all threads will hang until the interpreter gets the control back. As the threads run within a single process, it cannot take advantage of multiple processors to achieve parallelism. Despite having so many disadvantages, Ruby threads are an efficient and lightweight way of achieving parallelism in code.

Creating Ruby Threads

The Thread library of Ruby allows concurrent programming. It provides multiple threads of execution in a single process that share the same memory space and execute concurrently. The Thread class represents user-level threads.To start a new thread, the Thread.new call is used and a block is passed as a parameter to it. This block of code runs in the thread.

The following code illustrates multithreading in Ruby

def function_1
	count = 0
	while count<=2
		puts "Inside function_1"
		count += count
	end
end

def function_2
	count = 0
	while count<=2
		puts "Inside function_2"
		count += count
	end
end

thread1 = Thread.new{function_1()}
thread2 = Thread.new{function_2()}

thread1.join
thread2.join

Two threads are created and functions function_1 and fucntion_2 are passed as parameters. thread1 executes fucntion_1 and thread2 executes fucntion_2 . The following interleaved output is produced.


Inside function_1
Inside fucntion_1
Inside function_2
Inside function_2
Inside function_1
Inside fucntion_2

Threads share the global, instance and local variables that are in existence at the time the thread starts. Local variables created within a thread’s block are truly local to that thread. Each thread will have its own copy of these variables. Any number of arguments can be passed to the thread as parameters to Thread.new.

Thread life-cycle

A thread need not be started once it is created. It automatically begins running when the CPU resources become available.There are a number of methods in the thread class to manipulate the thread while it is running.

The ‘value’ method of the Thread object returns the value of the last method that is computed in the thread if the thread has run to completion. Otherwise, the method blocks and does not return until the thread has completed. The method Thread.current returns the Thread object that represents the current thread. The method Thread.main returns the Thread object that represents the main thread which is the initial thread of execution when the program started. Thread.status and Thread.alive give the status of a particular thread. A Ruby program can terminate even if its child threads are still in execution. In order to avoid such a situation, the 'join' method can be used. Join blocks the calling process until the thread that it is called on runs to completion. Alternatively a join timeout parameter can also be passed as an argument to this method to specify the amount of time to block the calling thread.


==Ruby Thread Models==

Thread Variables

Variables defined in the block that is passed to a thread are local to the thread and cannot be accessed by other threads. However, in Ruby it is possible to define variables in a block that are accessible to other threads. This is done by treating the thread object as if it were a Hash and writing to elements using []= and reading them using []. The following code illustrates this point.

Threads and Exceptions

In Ruby, exceptions are handled through the 'rescue' clause which is analogous to try and catch blocks in java. The code that might throw an exception is placed within 'begin' and 'end' statements and a handler (rescue clause) is written inside it. If an exception is raised, control is transferred to the rescue clause. example

There are two flags which determine the course of action in case of un-handled exceptions namely abort_on_exception and debug. If abort_on_exception flag is set to false, only the current thread is terminated by an un-handled exception and the rest continue to run. example However, if it is set to true, every single thread is killed and the entire process terminates. example

Mutual Exclusion

Critical section is a code segment in which shared data is accessed. The problem is to ensure that when one thread is executing in its critical section, no other process is allowed to execute in its critical section. Mutual exclusion is a way of making sure that no two threads are in their critical section at the same time.

In Ruby, thread synchronization is achieved at the lowest level by using a global thread-critical condition. When the condition is set to true, the scheduler will not schedule any others threads to run and only one thread has access to its critical section. Although this method provides basic synchronization, it is not recommended as it is tricky to implement and requires a good amount of expertise on the part of the programmer. Ruby comes with several libraries to provide synchronization that can be readily included in our code. Some of them include the Monitor library, Sync library, Mutex_m library.

Monitors

A monitor is an object that provides synchronization in a multithreaded environment. It is a high level abstraction of three features- shared data, operations performed on the shared data and synchronization. The following code illustrates the need for synchronization

class Increment
	@@ count 
	def initialize
		@count = 0
	end

	def inc
		count +=1
	end
end

r = Increment.new
thread1 = Thread.new { 10.times{ r.inc}}
thread2 = Thread.new { 10.times{ r.inc}}
thread1.join
thread2.join

r.count	                 # outputs 17


If you consider the above code snippet, the correct order of execution is thread2 waits until thread1 finishes execution and then continues running. So the value of r.count should be 20. But in reality, before thread1 completes its execution cycle, thread2 pre-empts and accesses the count variable, thereby affecting the final value which is 17 in this case. This is because the statement count=+1 is not atomic. It is divided into three steps- fetching the value of count, setting it to the new value and writing back the new value. Any context switch that happens before the thread finishes executing the three steps results in an erroneous value. In order to synchronize this interleaved execution Ruby provides the Monitor library that can be included in your code. The following code illustrates the above example using Monitors.

require 'monitor'
class Increment < Monitor
	@@ count 
	def initialize
		@count = 0
	end

	def inc
		synchronize do
			count +=1
		end
	end
end

r = Increment.new
thread1 = Thread.new { 10.times{ r.inc}}
thread2 = Thread.new { 10.times{ r.inc}}
thread1.join
thread2.join

r.count	                               # outputs 20