Re: lock-based пример
От: remark Россия http://www.1024cores.net/
Дата: 19.08.08 23:57
Оценка:
Небольшой пример некой гипотетической бизнес-логики, отвечающей за хранение списка лицевых счетов, и осуществляющей переводы сумм с одного л/с на другой. Пример записан сразу в виде, пригодном для тестирования Relacy, но я думаю, что не составит труда представить, как код выглядел с использованием pthread API.
Так же в коде сразу присутствуют исправления допущенных ошибок (закомментированы), дабы впоследствие не сбивались номера строк.

1:#include "stdafx.h"
2:#include "../../relacy/relacy_std.hpp"
3:
4:class business_logic
5:{
6:public:
7:    typedef unsigned account_id_t;
8:    typedef double balance_t;
9:
10:    business_logic()
11:    {
12:        rl::pthread_rwlock_init(&accounts_guard, 0, $);
13:    }
14:
15:    ~business_logic()
16:    {
17:        rl::pthread_rwlock_destroy(&accounts_guard, $);
18:    }
19:
20:    bool add_account(account_id_t acc_id, balance_t balance)
21:    {
22:        rl::pthread_rwlock_wrlock(&accounts_guard, $);
23:        if (accounts.find(acc_id) != accounts.end())
24:        {
25:            rl::pthread_rwlock_unlock(&accounts_guard, $);
26:            return false;
27:        }
28:        accounts[acc_id].balance = balance;
29:        rl::pthread_rwlock_unlock(&accounts_guard, $);
30:        return true;
31:    }
32:
33:    bool transfer_balance(account_id_t acc_id1, account_id_t acc_id2, balance_t amount)
34:    {
35:        //if (acc_id1 == acc_id2)
36:        //    return true;
37:        rl::pthread_rwlock_rdlock(&accounts_guard, $);
38:        if (accounts.find(acc_id1) == accounts.end()
39:            || accounts.find(acc_id2) == accounts.end())
40:        {
41:            rl::pthread_rwlock_unlock(&accounts_guard, $);
42:            return false;
43:        }
44:        account_info& acc1 = accounts[acc_id1];
45:        account_info& acc2 = accounts[acc_id2];
46:        //if (acc_id1 > acc_id2)
47:        {
48:            rl::pthread_mutex_lock(&acc1.mtx, $);
49:            rl::pthread_mutex_lock(&acc2.mtx, $);
50:        }
51:        //else
52:        //{
53:        //    rl::pthread_mutex_lock(&acc2.mtx, $);
54:        //    rl::pthread_mutex_lock(&acc1.mtx, $);
55:        //}
56:        rl::pthread_rwlock_unlock(&accounts_guard, $);
57:
58:        acc1.balance -= amount;
59:        acc2.balance += amount;
60:
61:        rl::pthread_mutex_unlock(&acc1.mtx, $);
62:        rl::pthread_mutex_unlock(&acc2.mtx, $);
63:        return true;
64:    }
65:
66:private:
67:    struct account_info
68:    {
69:        balance_t balance;
70:        rl::pthread_mutex_t mtx;
71:
72:        account_info()
73:            : balance()
74:        {
75:            rl::pthread_mutex_init(&mtx, 0, $);
76:        }
77:
78:        account_info(account_info const& acc)
79:            : balance(acc.balance)
80:        {
81:            rl::pthread_mutex_init(&mtx, 0, $);
82:        }
83:
84:        ~account_info()
85:        {
86:            rl::pthread_mutex_destroy(&mtx, $);
87:        }
88:    };
89:
90:    typedef std::map<account_id_t, account_info> account_map_t;
91:    account_map_t accounts;
92:    rl::pthread_rwlock_t accounts_guard;
93:};
94:
95:struct business_logic_test : rl::test_suite<business_logic_test, 2>
96:{
97:    business_logic bl;
98:
99:    static size_t const account_count = 4;
100:
101:    void before()
102:    {
103:        for (size_t i = 0; i != account_count; ++i)
104:        {
105:            bool rv = bl.add_account(i, i * 10.0);
106:            RL_ASSERT(rv);
107:        }
108:    }
109:
110:    void thread(unsigned /*index*/)
111:    {
112:        business_logic::account_id_t acc1 = rl::rand(account_count);
113:        business_logic::account_id_t acc2 = rl::rand(account_count);
114:        bool rv = bl.transfer_balance(acc1, acc2, 1.0);
115:        RL_ASSERT(rv);
116:    }
117:};
118:
119:int main()
120:{    
121:    rl::simulate<business_logic_test>();
122:}
123:


При запуске теста Relacy детектирует рекурсивный захват нерекурсивного мьютекса. Из анализа истории выполнения видно, что рекурсивный захват происходит в строчках 48 и 49, т.е. когда номера л/с совпадают.

struct business_logic_test
RECURSION ON NON-RECURSIVE MUTEX
iteration: 3

execution history:
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [0] 1: [CTOR BEGIN]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [1] 1: [CTOR END]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [2] 1: [BEFORE BEGIN]
..\peterson.cpp(22) : [3] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [4] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [5] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [6] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [7] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [8] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [9] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [10] 1: <00358008> mutex: exclusive unlock
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [11] 1: [BEFORE END]
..\peterson.cpp(37) : [12] 0: <00358008> mutex: shared lock
..\peterson.cpp(37) : [13] 1: <00358008> mutex: shared lock
..\peterson.cpp(48) : [14] 0: <00357E88> mutex: exclusive lock
..\peterson.cpp(49) : [15] 0: <00357F48> mutex: exclusive lock
..\peterson.cpp(56) : [16] 0: <00358008> mutex: shared unlock
..\peterson.cpp(48) : [17] 1: <00357F08> mutex: exclusive lock
..\peterson.cpp(49) : [18] 1: <00357F08> mutex: recursive exclusive lock
..\peterson.cpp(49) : [19] 1: RECURSION ON NON-RECURSIVE MUTEX

thread 0:
..\peterson.cpp(37) : [12] 0: <00358008> mutex: shared lock
..\peterson.cpp(48) : [14] 0: <00357E88> mutex: exclusive lock
..\peterson.cpp(49) : [15] 0: <00357F48> mutex: exclusive lock
..\peterson.cpp(56) : [16] 0: <00358008> mutex: shared unlock

thread 1:
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [0] 1: [CTOR BEGIN]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [1] 1: [CTOR END]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [2] 1: [BEFORE BEGIN]
..\peterson.cpp(22) : [3] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [4] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [5] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [6] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [7] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [8] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [9] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [10] 1: <00358008> mutex: exclusive unlock
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [11] 1: [BEFORE END]
..\peterson.cpp(37) : [13] 1: <00358008> mutex: shared lock
..\peterson.cpp(48) : [17] 1: <00357F08> mutex: exclusive lock
..\peterson.cpp(49) : [18] 1: <00357F08> mutex: recursive exclusive lock
..\peterson.cpp(49) : [19] 1: RECURSION ON NON-RECURSIVE MUTEX



Хорошо, раскомментируем строчки:
35:        if (acc_id1 == acc_id2)
36:            return true;


Теперь Relacy выводит следующее. Из истории выполнения видно, что происходит дедлок, т.к. первый поток вначале захватывает мьютекс 00357E88, и потом пытается захватить мьютекс 00357EC8; а второй поток вначале захватывает мьютекс 00357EC8, а потом пытается захватить мьютекс 00357E88.

struct business_logic_test
DEADLOCK (deadlock detected)
iteration: 135

execution history:
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [0] 1: [CTOR BEGIN]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [1] 1: [CTOR END]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [2] 1: [BEFORE BEGIN]
..\peterson.cpp(22) : [3] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [4] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [5] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [6] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [7] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [8] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [9] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [10] 1: <00358008> mutex: exclusive unlock
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [11] 1: [BEFORE END]
..\peterson.cpp(37) : [12] 1: <00358008> mutex: shared lock
..\peterson.cpp(37) : [13] 0: <00358008> mutex: shared lock
..\peterson.cpp(48) : [14] 1: <00357EC8> mutex: exclusive lock
..\peterson.cpp(48) : [15] 0: <00357E88> mutex: exclusive lock
..\peterson.cpp(49) : [16] 1: <00357E88> mutex: blocking
..\peterson.cpp(49) : [17] 1: blocking current thread
..\peterson.cpp(49) : [18] 0: <00357EC8> mutex: blocking
..\peterson.cpp(49) : [19] 0: blocking current thread
..\peterson.cpp(49) : [20] 0: DEADLOCK (deadlock detected)

thread 0:
..\peterson.cpp(37) : [13] 0: <00358008> mutex: shared lock
..\peterson.cpp(48) : [15] 0: <00357E88> mutex: exclusive lock
..\peterson.cpp(49) : [18] 0: <00357EC8> mutex: blocking
..\peterson.cpp(49) : [19] 0: blocking current thread
..\peterson.cpp(49) : [20] 0: DEADLOCK (deadlock detected)

thread 1:
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [0] 1: [CTOR BEGIN]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [1] 1: [CTOR END]
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [2] 1: [BEFORE BEGIN]
..\peterson.cpp(22) : [3] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [4] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [5] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [6] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [7] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [8] 1: <00358008> mutex: exclusive unlock
..\peterson.cpp(22) : [9] 1: <00358008> mutex: exclusive lock
..\peterson.cpp(29) : [10] 1: <00358008> mutex: exclusive unlock
c:\root\relacy_1_0\rrd\relacy\context.hpp(385) : [11] 1: [BEFORE END]
..\peterson.cpp(37) : [12] 1: <00358008> mutex: shared lock
..\peterson.cpp(48) : [14] 1: <00357EC8> mutex: exclusive lock
..\peterson.cpp(49) : [16] 1: <00357E88> mutex: blocking
..\peterson.cpp(49) : [17] 1: blocking current thread



Хорошо, раскомментируем строчки, которые отвечают за правильный захват мьютексов:
46:        if (acc_id1 > acc_id2)
47:        {
48:            rl::pthread_mutex_lock(&acc1.mtx, $);
49:            rl::pthread_mutex_lock(&acc2.mtx, $);
50:        }
51:        else
52:        {
53:            rl::pthread_mutex_lock(&acc2.mtx, $);
54:            rl::pthread_mutex_lock(&acc1.mtx, $);
55:        }


Запускаем — тест успешно пройден!

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.