The purpose of this lab is to:
Create a folder called lab11 inside your cs150 folder.
10 points, individual
Once more, we will write the classic Hello, World program, but this time we will say hello from multiple processes. In this program, you will ask the user for a number of processes to create (n), and a name. Then you will create n processes, each of which will print "Hello, <name>, from Process <pid>". (A pid is a process id number.) Save this program in a file called hello.py
First, you will need to import the multiprocessing module, like so:
from multiprocessing import *You will need to define a function that does whatever you would like your process to do. For you, this will look something like:
def HelloWorld(name): #print Hello to Name from process idTo create a new process, p, that runs the function:
p = Process(target=HelloWorld, args=(name,))There are a couple of new things that we use when calling a process. First, it uses what are called "named arguments". We specify that the first parameter is the target, or function we'd like to use, and the second parameter is the paramters, or args, that we are passing that function. Secondly, to pass in the arguments to the target function, we use what is called a "tuple". This is basically a list, but with parenthesis instead of brackets. To specify a tuple of 1, we just add a comma after the first argument.
Next, to start process p, write:
p.start()To get the process id number, use
current_process().pid
15 points, individual
Recall that in Lab 3, we generated the value of pi by simulating dart throws. In this lab, you will create a program to do this in parallel, using Python's process pools. You will also look at how long it takes different numbers of processes to complete the task. Your final product, monte.py, will be a python program that takes the number of darts to throw from the user, and prints out what the approximate value of pi based on that number of throws is, as quickly as possible, as well as how long it took the program to generate that result. You will also turn in a file named monte_readme.txt in which will you write how long it took your program to approximate pi for 10,000,000 throws, using 1, 2, 4, 8, 20, 40, 100 and 200 processes.
A process pool is a way to create a number of threads, and have them all do the same task. To create a pool of processes, where n is the number of processes you wish to create:
pool = Pool(processes=n)To run the processes, you will use the Pool.map function. The map function takes in a function, f, and a list of arguments, with the length of the list equal to the number of processes. (Essentially, you are handing in separate arguments for each process, in the form of a list. Note that this means your function must take a single argument, although that argument may itself be a tuple or a list.) It returns a list of the return values from the processes. This will look like:
results = pool.map(f, args_list)
To measure how long it takes to approximate pi, you will use the time module. First:
import timeBefore you begin your calculations, get the start time:
start = time.time()After you are done, get the end time:
end = time.time()Subtract start from end to get the number of seconds that have elapsed.
elapsed = end - start
The details of implementing a solution are up to you, but here is a suggested outline of how to approach the problem. As usual, think about the 6 steps of program devepopment, and test each piece as you go.
It frequently makes sense for processes to be able to access the same variables, so they can work together. However, this can be very dangerous, as we will get incorrect results if two or more processes modify the same variable at the same time. To avoid this, we will use something called a Lock, which will only allow one process at a time to access a variable.
In this program, you will create an Account class, which simulates a bank account. You will create methods to add and withdraw money from the account. You will then create multiple processes which will act as users of the (same) account. Each process will withdraw a random amount of money from the account, wait a random amount of time, and then add a random amount of money to the account. It will do this 3 times. They will each use their pid for the customer number. The output of this program should look something like:
Customer 25928 withdrew 94 balance 6 Customer 25929 withdrew 6 balance 0 Customer 25929 added 68 balance 68 Customer 25929 withdrew 15 balance 53 Customer 25929 added 51 balance 104 Customer 25929 withdrew 85 balance 19 Customer 25930 withdrew 19 balance 0 Customer 25931 withdrew 0 balance 0 Customer 25932 withdrew 0 balance 0 Customer 25928 added 100 balance 100 Customer 25928 withdrew 83 balance 17 Customer 25929 added 79 balance 96 Customer 25931 added 31 balance 127 Customer 25931 withdrew 36 balance 91 Customer 25931 added 88 balance 179 Customer 25931 withdrew 76 balance 103 Customer 25931 added 2 balance 105 Customer 25930 added 41 balance 146 Customer 25930 withdrew 47 balance 99 Customer 25932 added 90 balance 189 Customer 25932 withdrew 12 balance 177 Customer 25928 added 17 balance 194 Customer 25928 withdrew 63 balance 131 Customer 25930 added 29 balance 160 Customer 25930 withdrew 17 balance 143 Customer 25930 added 0 balance 143 Customer 25932 added 8 balance 151 Customer 25932 withdrew 5 balance 146 Customer 25932 added 56 balance 202 Customer 25928 added 59 balance 261
In order to have this work, we need two things: shared memory, and synchronization. Shared memory will allow our processes to all change the balance of the account. Synchronization will prevent them from coming up with the wrong result. For example, when I run my program without synchronization, I get the following output:
Customer 25857 removed 71 balance 29 Customer 25858 removed 29 balance 0 Customer 25859 removed 0 balance 0 Customer 25860 removed 0 balance 0 Customer 25861 removed 0 balance 0 Customer 25858 added 71 balance 71 Customer 25858 removed 38 balance 33 Customer 25857 added 57 balance 90 Customer 25857 removed 54 balance 36 Customer 25858 added 58 balance 94 Customer 25858 removed 8 balance 86 Customer 25859 added 90 balance 176 Customer 25859 removed 77 balance 99 Customer 25859 added 30 balance 129 Customer 25859 removed 7 balance 122 Customer 25861 added 29 balance 151 Customer 25861 removed 24 balance 127 Customer 25860 added 57 balance 184 Customer 25860 removed 98 balance 86 Customer 25861 added 23 balance 109 Customer 25861 removed 17 balance 92 Customer 25861 added 29 balance 121 Customer 25859 added 12 balance 133 Customer 25860 added 69 balance 133 Customer 25860 removed 59 balance 74 Customer 25857 added 72 balance 146 Customer 25857 removed 0 balance 146 Customer 25860 added 24 balance 170 Customer 25858 added 52 balance 222 Customer 25857 added 4 balance 226
Note there are multiple instances of the wrong balance being calculated, for instance:
Customer 25859 added 12 balance 133 Customer 25860 added 69 balance 133This happens when we switch processes before the first process is done with its calculation, resulting in both processes using the orignal starting balance - essentially the first transaction is over written by the second. In order to fix this, we must use a lock when we are altering the balance, to ensure only once process at a time can change the balance. Note: We will need to use processes directly for this part of the lab, rather than using a process pool.
For shared memory, we will use the RawValue type available in python's multiprocessing module. It takes a code that indicates the type of the variable we want to share, and a value. To create a shared integer of value 0:
v = RawValue("i",0)
To access the value of that integer:
v.value
A lock is an object that only one process can "hold" at a time. To create a lock:
l = Lock()Now, if we want to use that lock so that only a single process can change the value of v, we have a process acquire the lock before it alters the value, and release it afterwards, like so:
l.acquire() v.value = v.value - 10 l.release()Note that it is VERY important you always release the lock after you acquire it - otherwise, no other processes will be able to acquire the lock, and your program will hang forever.
Recall that we want our customers to wait a random amount between withdrawing and adding money. To do this, we will use the time.sleep method, which causes a process to "sleep" (i.e. do nothing) for a specified amount of time. You will need to make sure to import the time class. To sleep for n seconds:
time.sleep(n)
If you followed the Honor Code in this assignment, insert a paragraph attesting to the fact within one of your .py files.
I affirm that I have adhered to the Honor Code in this assignment.
You now just need to electronically handin all your files. As a reminder
% cd # changes to your home directory % cd cs150 # goes to your cs150 folder % handin # starts the handin program # class is 150 # assignment is 11 # file/directory is lab11 % lshand # should show that you've handed in something
You can also specify the options to handin from the command line
% cd ~/cs150 # goes to your cs150 folder % handin -c 150 -a 11 lab11
hello.py monte.py monte_readme.txt account.py