מבוא מורחב למדעי המחשב
description
Transcript of מבוא מורחב למדעי המחשב
מבוא מורחב למדעי המחשב
14תרגול
Serial Programming
• Expressions are evaluated in a well-known order
• One expression at a time
• Life are easy (though slow)
Concurrent (Parallel) Computation
• Some code parts are “parallel” to each other
• Order of evaluation is not well-defined
• Typically depends on an internal OS mechanism which we can assume (almost) nothing about
Why parallel programming?
Why parallel programming?
Because it can be faster!(define (P i) (let ((counter (+ 1 (* (- i 1) (power 10 9)))) (upto (* i (power 10 9)))) (define (iter) (if (< counter upto) (begin (if (prime? counter) (display counter) #f) (increment-counter) (iter)) 'done)) (iter)))
(parallel-execute (P 1) (P 2) ... (P 10))
faster• Can be faster even on a single computer (dual-
core)
• Even on a single-core, things can happen in parallel
• Mainly CPU vs. I\O (disk access, communication…)
• But we need to parallelize wisely
Always parallelize?
• No• Parallel programs are harder to write and
are more error prone• Also, if designed badly, can be even
slower than sequential• Parallelize when you can gain something,
or when you have to
Why Parallelize?
• Because life are parallel!
• Say that you’ve built a desktop application
• Receives a query from the user, processes it and returns an answer
MindReader
(define (MindReader) (display “Enter your name”) (let ((name (read))) (ReadMind name)) (MindReader))
But reading minds takes time…
(define (MindReader) (display “Enter your name”) (let ((name (read))) (begin (display “Thinking…”) (ReadMind name))) (MindReader))
Put it on the web!(define (MindReader) (display “Enter your name”) (let (name (readMessage)) (begin (display “Thinking…”)
(let ((result (ReadMind name)))(display result))))
(MindReader))
Won’t work
• Say that reading minds takes only 5 seconds
• If only 20 people asked for it before I did, I should wait almost 2 minutes
• And I don’t even get a message• And probably my message is thrown away• Parallel Programming to the rescue!• But how?
Tactics• MindReader is a black-box, can’t change it
• But can separate web-interface from reading minds
• One process will receive messages, the other will read minds, and a third will print results
• How to communicate?
Queue Helper ProceduresHidden inside the abstraction(define (front-ptr q) (cadr q))(define (rear-ptr q) (cddr q))
(define (set-front-ptr! q item) (set-car! (cdr q) item))(define (set-rear-ptr! q item) (set-cdr! (cdr q) item))
queue
c dba
front-ptr
rear-ptr
Queue implementation (define (make-queue) (cons 'queue (cons null null)))(define (queue? q) (and (pair? q) (eq? 'queue (car q))))
(define (empty-queue? q) (if (not (queue? q)) (error "object not a queue:" q) (null? (front-ptr q))))
(define (front-queue q) (if (empty-queue? q) (error "front of empty queue:" q) (car (front-ptr q))))
Queue implementation – Insert(define (insert-queue! q elt) (let ((new-pair (cons elt nil))) (cond ((empty-queue? q) (set-front-ptr! q new-pair) (set-rear-ptr! q new-pair) ‘ok) (else (set-cdr! (rear-ptr q) new-pair) (set-rear-ptr! q new-pair) ‘ok))))
e
new-pair
queue
c dba
front-ptr
rear-ptrrear-ptr
pop-queue!
(define (pop-queue! q) (let ((result (front-queue q))) (begin (delete-queue! q) result))))
Application Workflow
ReadAndSave ExtractAndProcess ExtractAndPrint
requests results
New interface function
(define (ReadAndSave q) (display “Enter your name”) (let ((name (readMessage))) (begin (display “Your message was received”)
(insert-queue! q name))) (ReadAndSave q)
)
Processing message
(define (ExtractAndProcess q-in q-out) (let ((name (pop-queue! q-in)) (let ((result (ReadMind name))) (insert-queue! q-out result))) (ExtractAndProcess q-in q-out))
Printing the result
(define (ExtractAndPrint q) (let ((result (pop-queue! q))) (display result)) (ExtractAndPrint q))
Putting it all together(define (myWebApplication) (let ((q-requests (make-queue)) (q-results (make-queue)))
(parallel-execute (ReadAndSave q-requests)
(ExtractAndProcess q-requests q-results)
(ExtractAndPrint q-results))))
A Problem
• One process might try to access the queue while the other is inserting
• A message might get lost
• We need Mutual Exclusion
Mutual Exclusion
• The problem rises in case of a shared object
• We can’t have two processes writing to it in the same time
• In the vast majority of cases, read+write is forbidden as well
• read+read is sometimes ok, depends on the case
Mutex• A synchronization object
• A process can request for access
• It will be granted access only if no one is holding the mutex
• Otherwise wait
New interface function(define (ReadAndSave q m) (display “Enter your name”) (let ((name (readMessage))) (begin (display “Your message was received”) (m 'begin) (insert-queue! q name) (m 'end)) (ReadAndSave q m))
New processing message(define (ExtractAndProcess q-in m-in q-out m-out) (m-in ‘begin) (let ((name (pop-queue! q-in)) (m-in ‘end) (let ((result (ReadMind name))) (begin (m-out ‘begin) (insert-queue! q-out result) (m-out ‘end)))) (ExtractAndProcess q-in m-in q-out m-out))
Printing the result(define (ExtractAndPrint q m) (m ‘begin) (let ((result (pop-queue! q))) (begin (m ‘end) (display result))) (ExtractAndPrint q m))
Putting it all together (new)(define (myWebApplication) (let ((q-requests (make-queue)) (m-requests (make-mutex)) (q-results (make-queue))) (m-results (make-mutex))) (parallel-execute (ReadAndSave q-requests m-requests)
(ExtractAndProcess q-requests m-requests q-results m-results)
(ExtractAndPrint q-results m-results))))
A better solution
• Put a mutex within the queue object
• We’ll create a new object: secure-mutex
• Don’t throw away the old queue, though
Secure-Queue implementation (define (make-secure-queue)
( cons (make-mutex) (cons ‘secure-queue (cons null null))
(define (mutex q) (car q)
(define (secure-queue? q) (and (pair? q) (pair? (cdr q)) (eq? ‘secure-queue
(cadr q))))
(define (empty-secure-queue? q) (if (not (secure-queue? q)) (error "object not a secure queue:" q) (begin ((mutex q) ‘begin) (let ((res (null? (front-ptr q)))) ((mutex q) ‘end) res) )))
front-secure-queue
(define (front-secure-queue q) (if (empty-secure-queue? q) (error "front of empty queue:" q) (begin ((mutex q) ‘begin) (car (front-ptr q) ((mutex q) ‘end))))
front-secure-queue
(define (front-secure-queue q) (if (empty-secure-queue? q) (error "front of empty queue:" q) (begin ((mutex q) ‘begin) (car (front-ptr q) ((mutex q) ‘end))))
Mind the gap!
front-secure-queue
(define (front-secure-queue q) (if (empty-secure-queue? q) (error "front of empty queue:" q) (begin ((mutex q) ‘begin) (car (front-ptr q) ((mutex q) ‘end))))
Mind the gap!
What about the return value?
front-secure-queue
(define (front-secure-queue q) ((mutex q) ‘begin) (if (empty-secure-queue? q) (begin ((mutex q) ‘end) (error "front of empty queue:" q)) (begin (car (front-ptr q)) ((mutex q) ‘end)))))
Not all mutex implementations support multiple
acquisition
front-secure-queue
(define (front-secure-queue q) ((mutex q) ‘begin) (if (empty-queue? q) (begin ((mutex q) ‘end) (error "front of empty queue:" q)) (begin (let ((ret (car (front-ptr q))) ((mutex q) ‘end) ret))))
Insert-secure-queue!(define (insert-secure-queue! q elt) (let ((new-pair (cons elt nil))) (cond ((empty-queue? q) (begin ((mutex q) ‘begin) (set-front-ptr! q new-pair) (set-rear-ptr! q new-pair) ((mutex q) ‘end) ‘ok) (else (begin ((mutex q) ‘begin) (set-cdr! (rear-ptr q) new-pair) (set-rear-ptr! q new-pair) ((mutex q) ‘end) ‘ok)) )))
Insert-secure-queue!(define (insert-secure-queue! q elt) (let ((new-pair (cons elt nil))) (cond ((empty-queue? q) (begin ((mutex q) ‘begin) (set-front-ptr! q new-pair) (set-rear-ptr! q new-pair) ((mutex q) ‘end) ‘ok) (else (begin ((mutex q) ‘begin) (set-cdr! (rear-ptr q) new-pair) (set-rear-ptr! q new-pair) ((mutex q) ‘end) ‘ok)) )))
Need to lock before calling
queue-empty?
Where to put the mutex?
• Sometimes difficult to decide
• Thumb rule – lock as lower as possible, and as little as possible
• But note that accessing the locks in real life is costly as well