Part 86 Multithreading in C#
What is a Process:
Process is what the operatin system uses to facilitate(幫助) the execution of a program by providing the resources required.Each process has unique process Id associated with(關聯) it. You can view the process within which a program is being executed using windows task manager.
What is Thread:
Thread is a light weight(輕量級) process.A process has at least one thread which is commonly called(通常被稱為) as main thread which actually executes the application code. A single process can hava multiple threads.
Please Note: All the threading related classes are present in(存在於) System.Threading namespace.
Part 87 Advantages and disadvantages of multithreading
Advantages of multithreading:
1, To maintain a responsive user interface(快速響應用戶界面)
2, To make effcient use of processor time while waiting for I/O operations to complete.(適當的利用處理器,在等待I / O操作完成的這段時間。)
3, To split large, CPU-bound tasks to be processed simultaneously on a machine that has multiple processors/cores(分割大cpu密集型任務處理的機器上同時有多個處理器/核心)
Disadvantages of multithreading:
1, On a single processor/core machine threading can affect performance negatively as there is overhead involved with context-switching.(在單個處理器/核心的機器,線程會對上下文切換開銷的性能有負面影響)
2, Have to write more lines of code to accomplish the same task.(需要編寫更多的代碼來完成相同的任務)
3,Multithreaded applications are difficult to write, understand, debug and maintain.(多線程應用程序很難寫,理解、調試和維護。)
Please Note: Only use multithreading when the advantages of doing so outweigh the disavantages.
Part 88 ThreadStart delegate
To create a Thread, create an instance of Thread class and to it's constructor pass the name of the function that we want the thread to execute.
class Program { static void Main(string[] args) { Thread t = new Thread(Number.Print); t.Start(); } } public class Number { public static void Print() { for(int i=0;i<10;i++) { Console.WriteLine(i); } } } Thread Thread t = new Thread(Number.Print);
t.Start();
We can rewrite the above line using ThreadStart delegate as shown below
Thead t1 = new Thread(new ThreadStart(Number.Print));
t1.Start();
Why a delegate need to be passed as a parameter to the Thread class constructor?
The purpose of creating a Thread is to execute a function. A delegate is a type safe function pointer, meaning it points to a function that the thread has to execute. In short, all threads require an entry point to start execution. Any thread you create will need an explicitly defined entry point i.e(那就是說) a pointer to the function where they should begin execution. So threads always require a delegate.
In the code below, we are not explicitly creating the ThreadStart delegage, then how is it working here?
Thread t1 = new Thread(Number.Print);
t1.Start();
It's working in spite of(盡管) not creating the ThreadStart delegage explictly because the framework is doing it automatically for us.
Other ways, We can also rewrite the same line using delegate() keyword
Thread t = new Thread(delegate(){Number.Print();});
The same line rewritten using lambda expression
Thread t = new Thread(()=>Number.Print());
Part 89 ParameterizedThreadStart delegate
Use ParameterizedThreadStart delegate to pass data to the thread function
class Program { static void Main(string[] args) { Console.WriteLine("input a target number:"); object target = Console.ReadLine(); ParameterizedThreadStart pt = new ParameterizedThreadStart(Number.Print);//really need to write that?No,you can just pass the function into Thread class. Thread t = new Thread(pt);//new Thread(Number.Print); t.Start(target); Console.ReadLine(); } } public class Number { public static void Print(object target) { int number = 0; if (int.TryParse(target.ToString(), out number)) { for (int i = 0; i < number; i++) { Console.WriteLine(i); } } } } View CodeHow we are not explictly creating an instance of ParameterizedThreadStart delegate.Then how is it working?
It's working because, the compiler implicitly converts new Thread(Number.Print); to new Thread(new ParameterizedThreadStart(Number.Print));
When to use ParameterizedThreadStart over ThreadStart delegate?
Use ParameterizedThreadStart delegate if you have some data to pass to the Thread function, otherwise just use ThreadStart delegate.
Please note: Using parameterizedThreadStart delegate and Thread.Start(Object) method to pass data to the Thread function is not type safe as they operate on object datatype and any type of data can be passed.
If you try to change the data type of the target parameter of Print() function from object to int, a compiler error will be raised as the signature of Print() function does not match with the signature of ParameterizedThreadStart delegate.
Part 90 Passing data to the Thread function in a type safe manner(in a manner..在某種程度上)
To pass data to the Thread function in a type safe manner, encapsulate the thread function and the data it needs in a helper class and use the ThreadStart delegate to execute the thread function.
class Program { static void Main(string[] args) { Console.WriteLine("input a target number:"); int target = Convert.ToInt32(Console.ReadLine()); Number number = new Number(target); Thread t = new Thread(number.Print); t.Start(); Console.ReadLine(); } } public class Number { int _target; public Number(int target) { this._target = target; } public void Print() { int number = 0; for (int i = 0; i < _target; i++) { Console.WriteLine(i); } } } View CodePart 91 Retrieving data from Thread function using callback method
Callback method to get data from thread
Main thread retrieves(檢索) the target number from the user.
Main thread creates a child thread and pass the target number to the child thread.
The child thread computes the sum of numbers and then returns the sum to the Main thread using callback function.
The callback method prints the sum of numbers.
Step 1: Create a callback delegate. The actual callback method signature should match with the signature of this delegate.
public delegate void SumOfNumberCallback(int sumOfNumber);
Step 2:Create a helper class to compute the sum of numbers and to call the callback method.
public class Number { int _target; SumOfNumberCallback _callBackMethod; public Number(int target, SumOfNumberCallback callBackMethod) { this._target = target; this._callBackMethod = callBackMethod; } public void PrintSumOfNumber() { int sum = 0; for (int i = 1; i <= _target; i++) { sum = sum + i; } if(_callBackMethod!=null) { _callBackMethod(sum); } } } View CodeStep 3:This class consumes the Number class create in Step 2
class Program { public static void Main(string[] args) { Console.WriteLine("input a target number:"); int target = Convert.ToInt32(Console.ReadLine()); SumOfNumberCallback callBack = new SumOfNumberCallback(PrintSum); Number number = new Number(target,callBack); Thread t = new Thread(number.PrintSumOfNumber); t.Start(); Console.ReadLine(); } public static void PrintSum(int sum) { Console.WriteLine("sum of number =" + sum); } } View CodePart 92 Significance of Thread Join and Thread IsAlive functions
Thread.Join & Thread.IsAlive functions
Join blocks the current thread and makes it wait until the thread on which Join method is invoked completes.Join method also has a overload where we can specify the timeout. If we don't specify the timeout the calling thread waits indefinitely(無限期地), until the thread on which Join() is invoked completes. This overloaded Join(int millisecondesTimeout) method returns boolean. True if the thread has terminated(終止) otherwise false.
Join is particularly(特別地) useful when we need to wait and collect result from a thread execution or if we need to do some clean-up after the thread has completed.
IsAlive returns boolean. True if the thread is still executing otherwise false.
public static void Main(string[] args) { Console.WriteLine("main start"); Thread t1 = new Thread(ThreadFunction1); t1.Start(); Thread t2 = new Thread(ThreadFunction2); t2.Start(); if(t1.Join(1000)) { Console.WriteLine("threadfunction1 end"); } else { Console.WriteLine("threadfunction1 still working"); } if(t1.IsAlive) { for (int i = 1; i < 10; i++) { Console.WriteLine("threadfunction1 still working..."); Thread.Sleep(500); } } Console.WriteLine("main end"); } public static void ThreadFunction1() { Console.WriteLine("threadfunction1 start"); Thread.Sleep(5000); Console.WriteLine("threadfunction1 end"); } public static void ThreadFunction2() { Console.WriteLine("threadfunction2 start"); } View CodePart 93 Protecting shared resources from concurrent access in multithreading
Part 94 Difference between Monitor and lock in C#
Part 95 Deadlock in a multithreaded program
class Program { static void Main(string[] args) { Console.WriteLine("main start"); Account a1 = new Account(001,10000); Account a2 = new Account(002,20000); AccountManager m1 = new AccountManager(a1,a2,5000); Thread t1 = new Thread(m1.Transfer); t1.Name = "t1"; AccountManager m2 = new AccountManager(a2, a1, 3000); Thread t2 = new Thread(m2.Transfer); t2.Name = "t2"; t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("main end"); } } class Account { public int ID { get; set; } public double Balance { get; set; } public Account(int id, double balance) { this.ID = id; this.Balance = balance; } public void WithDraw(double amount) { Balance -= amount; } public void Deposit(double amount) { Balance += amount; } } class AccountManager { public Account FromAccount { get; set; } public Account ToAccount { get; set; } public double AmountToTransfer { get; set; } public AccountManager(Account from,Account to,double amountToTransfer) { this.FromAccount = from; this.ToAccount = to; this.AmountToTransfer = amountToTransfer; } public void Transfer() { Console.WriteLine(Thread.CurrentThread.Name+"try to acquire lock on"+FromAccount.ID.ToString()); lock (FromAccount) { Console.WriteLine(Thread.CurrentThread.Name+" acquired lock on "+FromAccount.ID.ToString()); Console.WriteLine(Thread.CurrentThread.Name+" suspended for 1 second"); Thread.Sleep(1000); Console.WriteLine(Thread.CurrentThread.Name+"back in action and try to acquire lock on" +ToAccount.ID.ToString()); lock (ToAccount) { Console.WriteLine("this code will not execute"); FromAccount.WithDraw(AmountToTransfer); ToAccount.Deposit(AmountToTransfer); } } } } View Code
Part 96 How to resolve a deadlock in a multithreaded program
static void Main(string[] args) { Console.WriteLine("main start"); Account a1 = new Account(101,10000); Account a2 = new Account(102,20000); AccountManager m1 = new AccountManager(a1,a2,5000); Thread t1 = new Thread(m1.Transfer); t1.Name = "t1"; AccountManager m2 = new AccountManager(a2, a1, 3000); Thread t2 = new Thread(m2.Transfer); t2.Name = "t2"; t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine("main end"); } } class Account { public int ID { get; set; } public double Balance { get; set; } public Account(int id, double balance) { this.ID = id; this.Balance = balance; } public void WithDraw(double amount) { Balance -= amount; } public void Deposit(double amount) { Balance += amount; } } class AccountManager { public Account FromAccount { get; set; } public Account ToAccount { get; set; } public double AmountToTransfer { get; set; } public AccountManager(Account from,Account to,double amountToTransfer) { this.FromAccount = from; this.ToAccount = to; this.AmountToTransfer = amountToTransfer; } public void Transfer() { object _lock1, _lock2; if(FromAccount.ID<ToAccount.ID) { _lock1 = FromAccount; _lock2 = ToAccount; } else { _lock1 = ToAccount; _lock2 = FromAccount; } Console.WriteLine(Thread.CurrentThread.Name+"try to acquire lock on "+((Account)_lock1).ID.ToString()); lock (_lock1) { Console.WriteLine(Thread.CurrentThread.Name + " acquired lock on " + ((Account)_lock1).ID.ToString()); Console.WriteLine(Thread.CurrentThread.Name+" suspended for 1 second"); Thread.Sleep(1000); Console.WriteLine(Thread.CurrentThread.Name + "back in action and try to acquire lock on " + ((Account)_lock2).ID.ToString()); lock (_lock2) { Console.WriteLine(Thread.CurrentThread.Name + " acquired lock on " + ((Account)_lock2).ID.ToString()); FromAccount.WithDraw(AmountToTransfer); ToAccount.Deposit(AmountToTransfer); Console.WriteLine(Thread.CurrentThread.Name+" Transferd "+AmountToTransfer.ToString()+" from "+FromAccount.ID.ToString()+" to "+ToAccount.ID.ToString()); } } } View CodePart 97 Performance of a multithreaded program
class Program { static void Main(string[] args) { Stopwatch s = new Stopwatch(); s.Start(); EvenNumbersSum(); OddNumbersSum(); s.Stop(); Console.WriteLine("before using multiple threads"+s.ElapsedMilliseconds); s = new Stopwatch(); s.Start(); Thread t1 = new Thread(EvenNumbersSum); Thread t2 = new Thread(OddNumbersSum); t1.Start(); t2.Start(); t1.Join(); t2.Join(); s.Stop(); Console.WriteLine("after using multiple threads"+s.ElapsedMilliseconds); } public static void EvenNumbersSum() { double sum=0; for(int i=0;i<=50000000;i++) { if(i%2==0) { sum += i; } } Console.WriteLine("sum= "+sum); } public static void OddNumbersSum() { double sum = 0; for (int i = 0; i <= 50000000; i++) { if (i % 2 == 1) { sum += i; } } Console.WriteLine("sum= " + sum); } } View Code