Download Parallel Thinking with Multithreading

Document related concepts
no text concepts found
Transcript
Parallel Thinking
with
Java Multithreading
Introduction
• Algorithms which make use of more than one processor
• Very hot topic today because all chip manufacturers are producing “multicore” processors- (>1 processor per chip)
A little physics lesson
• To increase performance::::::
Smaller transistors

transistor density

faster processors

power consumption 

increased heat

unreliable processors
Solution = multicore computers
Can we parallelize following program?
c = a+b
d = a*b
e = d+2
Answer
• Yes
Can we parallelize following program?
c = a+b
d = c*b
e = d+2
Answer
• No.
• Such program’s are called inherently serial program
Why we need ever-increasing performance?
• Climate modeling
• century scale changes
• limited by minute scale timesteps
• Protein folding
• Mad Cow disease,
• allergies
• Drug discovery
• extensive computational analysis of genomes.
• Takes 10yrs and 2-3billion dollars for drug to market.
• Data analysis.
• terabytes of data : dynamic, unstructured
• cant store in relational db,
• faster analysis
Example
• Calculate sum of n numbers using p processors
Calculating the sum of an array of n integers
• Serial code:
• Serial Time = O(n)
How to do this in parallel?
• Algorithm1: Assume n = p
4
3
1
1
P0
P1
P2
P3
t1
Parallel Time = Tcomputation + Tcommunication
= O(n-1)
+ O(n-1)
= O(n-1)
= O(n)
4+3=7
Not so great compared to serial algorithm. Still O(n)
t2
7+1=8
t3
8+1=9
Can we do better?
• Algorithm2: Assume n = p
4
3
1
1
P0
P1
P2
P3
Parallel Time = O(log n)
t1
4+3=7
t2
7+2=9
1+1=2
Great time compared to serial algorithm.
• Algorithm1: Assume n > p (in practice n>>p)
• Assign n/p elements per process
• Each process calculate local sum of n/p elements
• Now each process has 1 element (local sum). So proceed as before to calculate global
sum
Local_sum= 7
4,3
P0
8
3,5
2
1,1
P1
P2
t1
7+8=15
t2
15+2=17
t3
17+3=20 Global_sum
3
1,2
P3
Parallel Time = Tcomputation + Tcommunication
= O(n/p)
+ O(p)
= O(n/p +p)
• Algorithm2: Assume n > p (in practice n>>p)
• Assign n/p elements per process
• Each process calculate local sum of n/p elements
• Now each process has 1 element (local sum). So proceed as before to calculate global
sum
Local_sum= 7
4,3
P0
7+8=15
8
3,5
2
1,1
P1
P2
2+3=5
15+5=20 Global_sum
3
1,2
P3
Parallel Time = Tcomputation + Tcommunication
= O(n/p)
+ O(logp)
= O(n/p + logp)
Shared memory vs Distributed Memory
Shared memory
Distributed memory
Java Multithreading
• Running Threads
• A thread
• is a program unit that is executed independently of other parts of the program.
• The jvm executes each thread for a short amount of time and then switches
to another thread. This gives the illusion of executing the threads in parallel
to each other.
• Actually, if a computer has multiple central processing units (CPUs), then
some of the threads can run in parallel, one on each processor.
Running a Thread
1. Implement a class that implements the Runnable interface:
public interface Runnable
{
void run();
}
2. Place the code for your task into the run method of your class:
public class MyRunnable implements Runnable
{
public void run()
{
Task statements
. . .
}
}
3. Create an object of your subclass:
Runnable r = new MyRunnable();
4. Construct a Thread object from the Runnable object:
Thread t = new Thread(r);
5. Call the start method to start the thread:
t.start();
• A program to print a time stamp and “Hello World” once a
second for ten seconds:
Fri
Fri
Fri
Fri
Fri
Fri
Fri
Fri
Fri
Fri
Dec
Dec
Dec
Dec
Dec
Dec
Dec
Dec
Dec
Dec
28
28
28
28
28
28
28
28
28
28
23:12:03
23:12:04
23:12:05
23:12:06
23:12:07
23:12:08
23:12:09
23:12:10
23:12:11
23:12:12
PST
PST
PST
PST
PST
PST
PST
PST
PST
PST
2012
2012
2012
2012
2012
2012
2012
2012
2012
2012
Hello,
Hello,
Hello,
Hello,
Hello,
Hello,
Hello,
Hello,
Hello,
Hello,
World!
World!
World!
World!
World!
World!
World!
World!
World!
World!
Program that generates previous .. .
import java.util.Date;
public class MyRunnable implements Runnable
{
public void run()
{
try
{
for (int i = 1; i <= 10; i++)
{
Date now = new Date();
System.out.println(now + " Hello, World!");
Thread.sleep(1000);
//1000ms = 1 s
//throws java.lang.InterruptedException
}
}
catch(Exception e)
{
}
}
}
public static void main(String[] args)
{
Runnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
}
Output
Run method
• Sleeping thread can generate an InterruptedException
• Catch the exception
• Terminate the thread
public void run()
{
try
{
Task statements
}
catch (InterruptedException exception)
{
}
Clean up, if necessary
}
Programming Question
• Write a class GreetingRunnable, where you create two threads t1, t2
that run in parallel. Each thread prints a greeting message repeatedly
for 10 times with a delay of 1 second. The greeting for t1 is “Hello”
and for t2 is “Goodbye”.
Find class template
• i.e for each thread:
•
Loop 10 times through task actions:
• Print a time stamp
• Print the greeting
• Wait a second
Class Template
import java.util.Date;
/**
A runnable that repeatedly prints a greeting.
*/
public class GreetingRunnable implements Runnable
{
private static final int REPETITIONS = 10;
private static final int DELAY = 1000;
private String greeting;
/**
Constructs the runnable object.
@param aGreeting the greeting to display
*/
public GreetingRunnable(String aGreeting)
{
greeting = aGreeting;
}
public void run()
{
try
{
//TODO: print timestamp and greeting for REPETITIONS repetitions with a delay DELAY between repetitions
}
catch (InterruptedException exception)
{
}
}
public static void main(String[] args)
{
//TODO: create two GreetingRunnable objects r1, r2 with greetings "Hello" and "GoodBye" respectively
//TODO: Assign r1 to thread t1, and r2 to t2 respectively
//TODO: start threads t1 and t2
}
}
Answer
import java.util.Date;
public class GreetingRunnable implements Runnable
{
private static final int REPETITIONS = 10;
private static final int DELAY = 1000;
private String greeting;
/**
Constructs the runnable object.
@param aGreeting the greeting to display
*/
public GreetingRunnable(String aGreeting)
{
greeting = aGreeting;
}
public void run()
{
try
{
//print greeting for REPETITIONS repetitions with a delay DELAY
for (int i = 1; i <= REPETITIONS; i++)
{
Date now = new Date();
System.out.println(now + " " + greeting);
Thread.sleep(DELAY);
}
}
catch (InterruptedException exception)
{
}
}
public static void main(String[] args)
{
//create two GreetingRunnable objects r1, r2 with greetings "Hello" and "GoodBye" respectively
GreetingRunnable r1 = new GreetingRunnable("Hello");
GreetingRunnable r2 = new GreetingRunnable("Goodbye");
//assign r1, r2 to two thrads t1, t2 respectively
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
//start threads t1 and t2
t1.start();
t2.start();
}
}
Output for 2 runs
Are they different?
Thread Scheduler
• Thread scheduler:
• runs each thread for a short amount of time (a time slice)
• Then the scheduler activates another thread
• There will always be slight variations in running times –
especially when calling operating system services (e.g.
input and output)
• There is no guarantee about the order in which threads
are executed
Terminating Threads
• A thread terminates when its run method terminates
• Notify a thread that it should terminate:
t.interrupt();
• interrupt does not cause the thread to terminate – it sets a boolean
variable in the thread data structure
• The run method should check occasionally whether it has been
interrupted:
• Use the interrupted method
• An interrupted thread should release resources, clean up, and exit:
public void run()
{
for (int i = 1; i <= REPETITIONS && !Thread.interrupted(); i++)
{
Do work
}
Clean up
}
• Java does not force a thread to terminate when it is
interrupted
• It is entirely up to the thread what it does when it is
interrupted
• Interrupting is a general mechanism for getting the
thread’s attention
Race Conditions
• When threads share a common object, they can conflict with each other
• Sample program: multiple threads manipulate a bank account
• Create two sets of threads:
• Each thread in the first set repeatedly deposits $100
• Each thread in the second set repeatedly withdraws $100
Let us write a program to simulate this situation . . .
Programming Question
• Write class DepositRunnable
Template given
• makes periodic deposits to a bank account
• Write class WithdrawRunnable
Template given
• makes periodic withdrawals from a bank account
• Given:
• Class BankAccount
• Prints the deposit/withdraw amount and new balance when a transaction is made
• Class BankAccountThreadRunner
• Creates a BankAccount object and repatedly deposits and withdraws $100 from account
BankAccount class
public class BankAccount
{
private double balance;
public BankAccount()
{
balance = 0;
}
public void deposit(double amount)
{
System.out.print("Depositing " + amount);
double newBalance = balance + amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
}
public void withdraw(double amount)
{
System.out.print("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
}
public double getBalance()
{
return balance;
}
}
Copy and save in lec14 directory
BankAccountThreadRunner class
public class BankAccountThreadRunner
{
public static void main(String[] args)
{
BankAccount account = new BankAccount();
final double AMOUNT = 100;
final int REPETITIONS = 100;
final int THREADS = 100;
for (int i = 1; i <= THREADS; i++)
{
DepositRunnable d = new DepositRunnable(account, AMOUNT, REPETITIONS);
WithdrawRunnable w = new WithdrawRunnable(account, AMOUNT, REPETITIONS);
Thread dt = new Thread(d);
Thread wt = new Thread(w);
dt.start();
wt.start();
}
}
}
DepositRunnable class template
public class DepositRunnable implements Runnable
{
private static final int DELAY = 1;
private BankAccount account;
private double amount;
private int count;
/**
Constructs a deposit runnable.
@param anAccount the account into which to deposit money
@param anAmount the amount to deposit in each repetition
@param aCount the number of repetitions
*/
public DepositRunnable(BankAccount anAccount, double anAmount,int aCount)
{
account = anAccount;
amount = anAmount;
count = aCount;
}
public void run()
{
try
{
//TODO: repeatedly deposit <amount> count number of times.
//between deposits have a delay of 1 millisecond.
}
}
}
catch (InterruptedException exception) {}
WithdrawRunnable class template
public class WithdrawRunnable implements Runnable
{
private static final int DELAY = 1;
private BankAccount account;
private double amount;
private int count;
/**
Constructs a withdraw runnable.
@param anAccount the account from which to withdraw money
@param anAmount the amount to withdraw in each repetition
@param aCount the number of repetitions
*/
public WithdrawRunnable(BankAccount anAccount, double anAmount,int aCount)
{
account = anAccount;
amount = anAmount;
count = aCount;
}
public void run()
{
try
{
//TODO: repeatedly withdraw <amount> count number of times.
//between withdrawals have a delay of 1 millisecond.
}
}
}
catch (InterruptedException exception) {}
Answer - DepositRunnable
public class DepositRunnable implements Runnable
{
private static final int DELAY = 1;
private BankAccount account;
private double amount;
private int count;
/**
Constructs a deposit runnable.
@param anAccount the account into which to deposit money
@param anAmount the amount to deposit in each repetition
@param aCount the number of repetitions
*/
public DepositRunnable(BankAccount anAccount, double anAmount,
int aCount)
{
account = anAccount;
amount = anAmount;
count = aCount;
}
public void run()
{
try
{
for (int i = 1; i <= count; i++)
{
account.deposit(amount);
Thread.sleep(DELAY);
}
}
catch (InterruptedException exception) {}
}
}
Answer - WithdrawRunnable
public class WithdrawRunnable implements Runnable
{
private static final int DELAY = 1;
private BankAccount account;
private double amount;
private int count;
/**
Constructs a withdraw runnable.
@param anAccount the account from which to withdraw money
@param anAmount the amount to withdraw in each repetition
@param aCount the number of repetitions
*/
public WithdrawRunnable(BankAccount anAccount, double anAmount,
int aCount)
{
account = anAccount;
amount = anAmount;
count = aCount;
}
public void run()
{
try
{
for (int i = 1; i <= count; i++)
{
account.withdraw(amount);
Thread.sleep(DELAY);
}
}
catch (InterruptedException exception) {}
}
}
Expected output
• Normally, the program output looks somewhat like this:
Depositing 100.0, new balance is 100.0
Withdrawing 100.0, new balance is 0.0
Depositing 100.0, new balance is 100.0
Depositing 100.0, new balance is 200.0
Withdrawing 100.0, new balance is 100.0
...
Withdrawing 100.0, new balance is 0.0
• The end result should be zero, but sometimes the output
is messed up, and sometimes end result is not zero:
Depositing 100.0Withdrawing 100.0, new balance is 100.0, new balance
is -100.0
What we get:
What happened?
• Scenario to explain problem:
1. A deposit thread executes the lines:
System.out.print("Depositing " + amount);
double newBalance = balance + amount;
The balance variable is still 0, and the newBalance local variable is 100
Statement (balance = newBalance;) is not executed yet.
2. The deposit thread reaches the end of its time slice and a withdraw thread gains
control
3. The withdraw thread calls the withdraw method which withdraws $100 from the
balance variable; it is now -100
4. The withdraw thread goes to sleep
• Scenario to explain problem (cont.):
5. The deposit thread regains control and picks up where it was interrupted. It now
executes:
System.out.println(", new balance is " + newBalance);
balance = newBalance;
The balance variable is now 100 instead of 0 because the deposit method used
the old balance to compute the value of its local variable newBalance
Corrupting the Contents of
the balance Variable
Race Condition

Occurs if the effect of multiple threads on shared data depends on the order in
which they are scheduled

It is possible for a thread to reach the end of its time slice
in the middle of a statement

It may evaluate the right-hand side of an equation but not
be able to store the result until its next turn:
public void deposit(double amount)
{
balance = balance + amount;
System.out.print("Depositing " + amount + ", new balance is " + balance);
}

Race condition can still occur:
balance = the right-hand-side value
Synchronizing Object Access
• To solve problems such as the one just seen, use a lock object
• Lock object: used to control threads that manipulate shared resources
• In Java library: Lock interface and several classes that implement it
• ReentrantLock: most commonly used lock class
• Locks are a feature of Java version 5.0
• Earlier versions of Java have a lower-level facility for thread synchronization
• Typically, a Lock object is added to a class whose methods access shared
resources, like this:
public class BankAccount
{
private Lock balanceChangeLock;
. . .
public BankAccount()
{
balanceChangeLock = new ReentrantLock();
. . .
}
. . .
}
• Code that manipulates shared resource is surrounded by calls to lock and
unlock:
balanceChangeLock.lock();
try
{
Manipulate the shared resource.
}
finally
{
balanceChangeLock.unlock();
}
If code between calls to lock and unlock throws an exception, call to unlock never happens . To
overcome this problem, place call to unlock into a finally clause
• Code for deposit method:
public void deposit(double amount)
{
balanceChangeLock.lock();
try
{
System.out.print("Depositing " + amount);
double newBalance = balance + amount;
System.out.println(", new balance is "+ newBalance);
balance = newBalance;
}
finally
{
balanceChangeLock.unlock();
}
}
• When a thread calls lock, it owns the lock until it calls
unlock
• A thread that calls lock while another thread owns the
lock is temporarily deactivated
• Thread scheduler periodically reactivates thread so it can
try to acquire the lock
• Eventually, waiting thread can acquire the lock
Programming Question
• Synchronize thread access to BankAccount object using locks in our
BankAccount class
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount
{
private double balance;
private Lock balanceChangeLock;
public BankAccount()
{
balance = 0;
balanceChangeLock = new ReentrantLock();
}
public void deposit(double amount)
{
balanceChangeLock.lock();
try
{
System.out.print("Depositing " + amount);
double newBalance = balance + amount;
System.out.println(", new balance is "+ newBalance);
balance = newBalance;
}
finally
{
balanceChangeLock.unlock();
}
}
public void withdraw(double amount)
{
balanceChangeLock.lock();
try
{
System.out.print("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
}
finally
{
balanceChangeLock.unlock();
}
}
public double getBalance()
{
return balance;
}
}
Answer
Output
Correct final output.
However, intermediate results
show negative balances (WRONG!)
We should rather wait till current balance builds up
(with some deposits) if withdraw amount> current
balance
Avoiding Deadlocks
• A deadlock occurs if no thread can proceed because each
thread is waiting for another to do some work first
•
BankAccount example:
public void withdraw(double amount)
{
balanceChangeLock.lock();
try
{
while (balance < amount)
Wait for the balance to grow
System.out.print("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
}
}
finally
{
balanceChangeLock.unlock();
}
• How can we wait for the balance to grow?
• We can’t simply call sleep inside withdraw method;
Why?
• thread will block all other threads that want to use balanceChangeLock
• In particular, no other thread can successfully execute deposit
• Other threads will call deposit, but will be blocked until withdraw exits
• But withdraw doesn’t exit until it has funds available
• DEADLOCK
withdawThread
depositThread
Condition Objects
• To overcome problem, use a condition object
• Condition objects allow a thread to temporarily release a
lock, and to regain the lock at a later time
• Each condition object belongs to a specific lock object
• You obtain a condition object with newCondition method of Lock
interface:
public class BankAccount
{
private Lock balanceChangeLock;
private Condition sufficientFundsCondition;
. . .
public BankAccount()
{
balanceChangeLock = new ReentrantLock();
sufficientFundsCondition = balanceChangeLock.newCondition();
. . .
}
}
• It is customary to give the condition object a name that
describes condition to test; e.g. “sufficient funds”
• You need to implement an appropriate test
• As long as test is not fulfilled, call await on the condition
object:
public void withdraw(double amount)
{
balanceChangeLock.lock();
try
{
while (balance < amount)
{
sufficientFundsCondition.await();
}
. . .
}
finally
{
balanceChangeLock.unlock();
}
}
• Calling await
• Makes current thread wait
• Allows another thread to acquire the lock object
• To unblock, another thread must execute signalAll on the same condition
object :
sufficientFundsCondition.signalAll();
• signalAll unblocks all threads waiting on the condition
Answer
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount{
private double balance;
private Lock balanceChangeLock;
private Condition sufficientFundsCondition;
public BankAccount()
{
balance = 0;
balanceChangeLock = new ReentrantLock();
sufficientFundsCondition = balanceChangeLock.newCondition();
}
public void deposit(double amount)
{
balanceChangeLock.lock();
try
{
System.out.print("Depositing " + amount);
double newBalance = balance + amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
sufficientFundsCondition.signalAll();
}
finally
{
balanceChangeLock.unlock();
}
}
public void withdraw(double amount) throws InterruptedException
balanceChangeLock.lock();
try
{
while (balance < amount)
{
sufficientFundsCondition.await();
}
System.out.print("Withdrawing " + amount);
double newBalance = balance - amount;
System.out.println(", new balance is " + newBalance);
balance = newBalance;
}
finally
{
balanceChangeLock.unlock();
}
}
public double getBalance()
}
{
return balance;
}
{
Output (correct!)
Programming Question
• Write a program WordCount that counts the words in one or more
files. Start a new thread for each file. For example, if you call
java WordCount
• then the program might print
address.txt: 1052
Homework.java: 445
report.txt: 2099
Assuming those files were in current directory.
Find code templates for WrdCount.java and
WordCountRunnable next slide
Code Template - WordCount
/**
Counts how many words in a file.
*/
import java.io.File;
public class WordCount
{
public static void main(String argv[])
{
File folder = new File("."); //current folder
for (File file : folder.listFiles())
{
// TODO: create and assign each thread to read a file
}
}
}
Code Template - WordCountRunnable
import
import
import
import
java.util.Scanner;
java.io.IOException;
java.io.FileNotFoundException;
java.io.File;
/**
Counts how many words in a file.
*/
public class WordCountRunnable implements Runnable
{
private File file;
/**
Constructs a WordCountRunnable object with a file name.
@param aFilename the file name that needs to count word
*/
public WordCountRunnable(File file)
{
this.file = file;
}
public void run()
{
//TODO: count and print the count of words with file name
}
}
Answer
/**
Counts how many words in a file.
*/
import java.io.File;
public class WordCount
{
public static void main(String argv[])
{
File folder = new File("."); //current folder
for (File file : folder.listFiles())
{
WordCountRunnable wcr = new WordCountRunnable(file);
Thread t = new Thread(wcr);
t.start();
}
}
}
Answer
import
import
import
import
java.util.Scanner;
java.io.IOException;
java.io.FileNotFoundException;
java.io.File;
/**
Counts how many words in a file.
*/
public class WordCountRunnable implements Runnable
{
private File file;
/**
Constructs a WordCountRunnable object with a file name.
@param aFilename the file name that needs to count word
*/
public WordCountRunnable(File file)
{
this.file = file;
}
public void run()
{
int count = 0;
try
{
Scanner in = new Scanner(file);
while (in.hasNext())
{
in.next();
count++;
}
} catch (FileNotFoundException e)
{
System.out.println(file.getName() + " not found!");
}
System.out.println(file.getName() + ": " + count);
}
}
Programming Question
• Enhance the previous program so that the last active thread also
prints a combined count.
• Use locks to protect the combined word count and a counter of active
threads.
Answer
import java.util.concurrent.locks.ReentrantLock;
/**
Counts how many words in a file.
*/
public class WordCount
{
public static void main(String argv[])
{
WordCountRunnable.threadCount = 0;
WordCountRunnable.threadCountLock = new ReentrantLock();
WordCountRunnable.combinedWordCount = 0;
WordCountRunnable.combinedWordCountLock = new ReentrantLock();
for (int i = 0; i < argv.length; i++)
{
WordCountRunnable wcr = new WordCountRunnable(argv[i]);
Thread t = new Thread(wcr);
t.start();
}
}
}
Answer
import
import
import
import
import
java.io.IOException;
java.io.FileNotFoundException;
java.io.FileInputStream;
java.util.Scanner;
java.util.concurrent.locks.Lock;
public class WordCountRunnable implements Runnable{
public static int threadCount;
public static Lock threadCountLock;
public static int combinedWordCount;
public static Lock combinedWordCountLock;
private String filename;
public WordCountRunnable(String aFilename)
filename = aFilename;
}
{
public void run()
{
threadCountLock.lock();
threadCount++;
threadCountLock.unlock();
int count = 0;
try
{
Scanner in = new Scanner(new FileInputStream(filename));
while (in.hasNext())
{
in.next();
count++;
}
} catch (FileNotFoundException e)
{
System.out.println(filename + " not found!");
} catch (IOException e)
{
System.out.println(e);
}
System.out.println(filename + ": " + count);
combinedWordCountLock.lock();
combinedWordCount += count;
threadCountLock.lock();
threadCount--;
if (threadCount < 1)
System.out.println("Combined Total: " + combinedWordCount);
threadCountLock.unlock();
combinedWordCountLock.unlock();
}
}