|
| 1 | +#lang racket |
| 2 | +(require "mutex.rkt") |
| 3 | + |
| 4 | +;;; 设想 Peter 企图去交换 a1 和 a2,同时 Paul 并发地企图交换 a2 和 a1 |
| 5 | +;;; 如果对账户编号,并使进程先试图获取编号较小的账户,那么意味着账户的 |
| 6 | +;;; 顺序是确定的;即 Peter 和 Paul 都会先获取 a1 (假设 a1 编号更小), |
| 7 | +;;; 那么只有一个进程会获取成功,那么它就可以继续获取 a2, 从而避免死锁 |
| 8 | + |
| 9 | +(define (make-account-and-serializer id balance) |
| 10 | + (define (withdraw amount) |
| 11 | + (if (>= balance amount) |
| 12 | + (begin (set! balance (- balance amount)) |
| 13 | + balance) |
| 14 | + "Insufficient funds")) |
| 15 | + (define (deposit amount) |
| 16 | + (set! balance (+ balance amount)) |
| 17 | + balance) |
| 18 | + |
| 19 | + (let ((balance-serializer (make-serializer))) |
| 20 | + (define (dispatch m) |
| 21 | + (cond ((eq? m 'withdraw) withdraw) |
| 22 | + ((eq? m 'deposit) deposit) |
| 23 | + ((eq? m 'balance) balance) |
| 24 | + ((eq? m 'serializer) balance-serializer) |
| 25 | + ((eq? m 'id) id) |
| 26 | + (else (error "Unknown request -- MAKE-ACCOUNT" m)))) |
| 27 | + dispatch)) |
| 28 | + |
| 29 | +(define (exchange account1 account2) |
| 30 | + (let ((difference (- (account1 'balance) |
| 31 | + (account2 'balance)))) |
| 32 | + ((account1 'withdraw) difference) |
| 33 | + ((account2 'deposit) difference))) |
| 34 | + |
| 35 | +(define (serializer-exchange account1 account2) |
| 36 | + (let* ((id1 (account1 'id)) |
| 37 | + (id2 (account2 'id)) |
| 38 | + (small-account (if (< id1 id2) account1 account2)) |
| 39 | + (large-account (if (< id1 id2) account2 account1)) |
| 40 | + (s-serializer (small-account 'serializer)) |
| 41 | + (l-serializer (large-account 'serializer))) |
| 42 | + ;; always lock smaller Id first, then large Id |
| 43 | + ((s-serializer (l-serializer exchange)) account1 account2))) |
| 44 | + |
| 45 | +(module+ test |
| 46 | + (require rackunit) |
| 47 | + |
| 48 | + (test-case "Test for no-dead lock version exchange" |
| 49 | + (define account1 (make-account-and-serializer 1 40)) |
| 50 | + (define account2 (make-account-and-serializer 2 20)) |
| 51 | + (check-equal? (account1 'id) 1) |
| 52 | + (check-equal? (account2 'id) 2) |
| 53 | + |
| 54 | + (define t1 (thread (lambda ()(serializer-exchange account1 account2)))) |
| 55 | + (define t2 (thread (lambda ()(serializer-exchange account2 account1)))) |
| 56 | + |
| 57 | + ;; should avoid deadlock |
| 58 | + (thread-wait t1) |
| 59 | + (thread-wait t2) |
| 60 | + |
| 61 | + ;; exchange twice means each account has the original balance |
| 62 | + (check-equal? (account1 'balance) 40) |
| 63 | + (check-equal? (account2 'balance) 20) |
| 64 | + ) |
| 65 | + ) |
0 commit comments