C-这个简单的transaction()函数怎么能完全没有死锁呢



所以我有一个用C:编写的基本transaction()函数

void transaction (Account from, Account to, double amount) {
    mutex lock1, lock2;
    lock1 = get_lock(from);
    lock2 = get_lock(to);
    acquire(lock1);
        acquire(lock2);
            withdraw(from, amount);
            deposit(to, amount);
    release(lock2);
    release (lock1);
}

据我所知,该函数基本上是无死锁的,因为该函数先锁定一个帐户,然后再锁定另一个帐户(而不是锁定一个、进行更改,然后锁定另一帐户)。但是,如果这两个调用同时调用此函数:

transaction (savings_account, checking_account, 500);
transaction (checking_account, savings_account, 300);

有人告诉我,这将导致僵局。如何编辑此函数,使其完全没有死锁?

您需要创建对象的总顺序(在本例中为Account对象),然后根据总顺序始终以相同的顺序锁定它们。您可以决定将它们锁定在哪个订单中,但简单的做法是首先锁定总订单中排名第一的一个,然后锁定另一个。

例如,假设每个帐户都有一个帐号,它是一个唯一的*整数。(*意味着没有两个账户有相同的号码)然后你可以先锁定账号较小的账户。使用您的示例:

void transaction (Account from, Account to, double amount)
{
    mutex first_lock, second_lock;
    if (acct_no(from) < acct_no(to))
    {
        first_lock  = get_lock(from);
        second_lock = get_lock(to);
    }
    else
    {
        assert(acct_no(to) < acct_no(from)); // total ordering, so == is not possible!
        assert(acct_no(to) != acct_no(from)); // this assert is essentially equivalent
        first_lock  = get_lock(to);
        second_lock = get_lock(from);
    }
    acquire(first_lock);
    acquire(second_lock);
    withdraw(from, amount);
    deposit(to, amount);
    release(second_lock);
    release(first_lock);
}

因此,按照这个例子,如果checking_account有账号1,savings_account有账号2,那么transaction (savings_account, checking_account, 500);将首先锁定checking_account,然后锁定savings_aaccount,transaction (checking_account, savings_account, 300);也将首先锁定checking_account,然后锁定savengs_account。

如果你没有账号(比如说你使用Foo类而不是account类),那么你需要找到其他东西来建立总订单。如果每个对象都有一个名称,作为字符串,那么您可以进行字母比较,以确定哪个字符串是"less"。或者,您可以使用与>和<类似的任何其他类型;。

然而,非常重要的是,每个对象的值都是唯一的!如果两个对象在测试的字段中具有相同的值,那么它们在排序中处于同一位置。如果可能发生这种情况,那么它是"部分排序",而不是"全部排序",对于这个锁定应用程序,有一个全部排序是很重要的。

如果必要,您可以组成一个"键值",它是一个任意的数字,没有任何意义,但保证对该类型的每个对象都是唯一的。创建每个对象时,为其指定一个新的、唯一的值。

另一种选择是将该类型的所有对象保留在某种列表中。然后,它们的列表位置用于将它们放在一个总的排序中。(坦率地说,"键值"方法更好,但有些应用程序可能已经出于应用程序逻辑的目的将对象保留在列表中,因此在这种情况下可以利用现有列表。

(*如果你用一个字符串来确定总排序,那么它实际上不是O(1),但它与字符串的长度是线性的,并且常数w.r.t.是包含这些字符串的对象的数量。。。然而,根据您的应用程序,字符串长度可能比对象的数量更合理。)

您试图解决的问题称为用餐哲学家问题,这是一个众所周知的并发问题。

在您的情况下,最简单的解决方案是将获取更改为接收2个参数(to和from),并且只有当它可以同时获得两个锁时才返回,如果不能同时获得这两个锁,则不获得任何锁(因为这是可能发生死锁的情况,当获得1个锁并等待另一个锁时)。读一读关于用餐哲学家的问题,你就会明白为什么。

希望它能有所帮助!

最新更新