390 likes | 558 Views
מבוא מורחב למדעי המחשב. תרגול 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
E N D
מבוא מורחב למדעי המחשב תרגול 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? 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?
rear-ptr front-ptr queue a b c d Queue Helper Procedures Hidden 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 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))))
new-pair rear-ptr rear-ptr front-ptr queue e a b c d 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))))
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) (ExtractAndProcessq-requests q-results) (ExtractAndPrintq-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) ((mutexq) ‘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) ((mutexq) ‘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) ((mutexq) ‘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 ((mutexq) ‘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