0

I have to do some bank account transfers with threads and benchmarking the different results. I think that the time of syncronized solution with general lock has to be worse than one lock per account solution.

Here is my implementation with general lock:

pthread_mutex_t general_mutex;

typedef struct {
    int id;
    double balance;
} Account;

int NThreads = 400; /*Threads number*/

#define N 20             /*Accounts number*/
Account accounts[N];
void transfer(double money, Account* origin, Account* destini) {

    pthread_mutex_lock(&general_mutex); //Init general lock.

    bool wasPosible = withdraw(origin, money);
    if (wasPosible ) deposit(destini, money);

    pthread_mutex_unlock(&general_mutex); //End general lock.
}

Here is the implementation with individual lock per account:

typedef struct {
    int id;
    double balance;
    pthread_mutex_t mutex; // lock to use/modify vars
} Account;

int NThreads = 400; /*Threads number*/

#define N 20             /*Accounts number*/
Account accounts[N];
void transfer(double money, Account* origin, Account* destini) {

    if (from->id < to->id) {
         pthread_mutex_lock(&(origin->mutex));
         pthread_mutex_lock(&(destini->mutex));
    } else {
         pthread_mutex_lock(&(destini->mutex));
         pthread_mutex_lock(&(origin->mutex));
    }

    bool wasPosible = withdraw(origin, money);
    if (wasPosible ) deposit(destini, amount);

    pthread_mutex_unlock(&(origin->mutex));
    pthread_mutex_unlock(&(destini->mutex));
}

Why the general lock solution expends less time than the second one?

Thank you

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 3
    Unrelated to your question, but 400 threads sounds excessive, unless you're on an extremely high-end workstation or server. Remember that the OS have to schedule and switch between all those threads, and the more threads you have the more time is spent switching between them. – Some programmer dude Feb 28 '19 at 08:54
  • 1
    More related to your problem, in the second example each call to `transfer` does *more* work than in the first example. The condition and calling multiple functions with different lock patterns takes time. Not to mention that there's a chance of deadlocks in the second variant, since you don't always unlock in the reverse order of locking. – Some programmer dude Feb 28 '19 at 08:57
  • I don't see where can be a deadlock in the second one, can you give me some example? I'm agree that the condicion and order takes time, but if the number of accounts is big enought I think that the second one has to take less time. I reduce the number of threads but there isn't any change. – David Ciria Feb 28 '19 at 09:08
  • 1
    `double balance;` is not a good idea. `double` cannot hold all possible values exactly. For example `3.17` will be stored as `3.1699999999999999289457264239899814128875732421875`. You should use an integer variable and store the Cents instead of the Dollar. – mch Feb 28 '19 at 09:21
  • 1
    Locks should always be unlocked in the opposite order they are locked, to prevent deadlocks. This should be part of any concurrent programming 101 course or book or tutorial. – Some programmer dude Feb 28 '19 at 09:33
  • Where does the `from->id` and `to->id` come from? There is no criteria about having to check if a certain account is < another account. – user3629249 Mar 02 '19 at 00:51
  • I created a function that init all the bank account IDs but I don't include the code here because it's not rellevant. Each account has the ID of the position in array. Finally I solved the problem that was the computer where I executed the code. – David Ciria Mar 02 '19 at 10:00

1 Answers1

0

A locking operation is not free. In the second example you do twice as much locking/unlocking operation than in the first one. The other operations seem to be simple memory accesses, so they should not last very long.

My opinion is that in your system, you spend more time in the locks than in actual processing, so increasing the number of locks is not relevant. It could be different if transfer used slow io like disk or network.

BTW, as you were said in comment, 400 threads is probably again less efficient than a much smaller number. A rule of thumb is as much as the number of core that you will use, increased by a variable factor if the processing spends time in waiting for io - if no io never more than the available cores. And the upper limit is that the memory used by all the threads must not exceed the memory you want to use, and that the overhead of starting and synchronizing the threads stays much lower that the total processing time.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252