2

Consider the following example code:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <random>
#include <array>
#include <numeric>


int main()
{
    constexpr std::size_t num_mutices = 3;
    constexpr std::size_t num_entries = 6;
    constexpr std::size_t num_threads = 4;

    std::array<std::mutex, num_mutices> mutices;
    std::array<std::size_t, num_entries> data;

    // fill data with 1, 2, ...
    std::iota(data.begin(), data.end(), 1);

    // setup random generators
    std::random_device rd;
    std::vector<std::mt19937> rand_gens;
    for(std::size_t i = 0; i < num_threads; i++)
        rand_gens.push_back(std::mt19937(rd()));

    std::vector<std::thread> threads;
    for(std::size_t iThread = 0; iThread < num_threads; iThread++)
    {
        threads.emplace_back(std::thread([&data, &mutices, &rand_gens, iThread]()
        {
            // distribution for picking a random value from data
            std::uniform_int_distribution<std::size_t> dist(0,data.size()-1);   

            for(std::size_t a = 0; a < 100; a++)
            {
                auto iFirst = dist(rand_gens[iThread]);
                auto iSecond = dist(rand_gens[iThread]);

                std::unique_lock<std::mutex> lock1(mutices[iFirst % mutices.size()]);
                std::unique_lock<std::mutex> lock2(mutices[iSecond % mutices.size()]);
                std::lock(lock1, lock2);

                std::size_t tmp = data[iFirst];
                data[iFirst] = data[iSecond];
                data[iSecond] = tmp;

                mutices[iFirst % mutices.size()].unlock();
                mutices[iSecond % mutices.size()].unlock();
            }
        }));
    }

    for(std::size_t i = 0; i < num_threads; i++)
        threads[i].join();
}

Basically I have multiple threads accessing elements of a vector at random and swapping them. Of course this is just a minimal working example of what I want to do (adding edges to a bidirectional boost::graph).

The problem is that this program can run into a deadlock and I don't know how to account for that.

I think this can be done by using hierachical mutices. And I found the following nice explanation: Use Lock Hierarchies to Avoid Deadlock

It states:

Rule 1: While holding a lock on a mutex at level N, you may only acquire new locks on mutexes at lower levels

So the levels in our case are the indices of the mutices used. Thus I should calculate the min and max of the mutices to lock and obtain them in that order. However this still leads to deadlock as the following scenario shows:

Thread 1 wants to lock Mutex 1 and Mutex 3. Thread 2 wants to lock Mutex 2 and Mutex 3. Now T1 locks M1, T2 lock M2 and now both want to obtain M3.

I tried solve this by using try_lock and unlocking mutices again if the thread could not obtain all needed mutices but somehow I don't seem to get it :P.

Ace7k3
  • 424
  • 4
  • 10
  • 2
    Those rules aren't very clear, I think the better way to remember it is with 1 rule: "always lock your mutexes in the same order" – UKMonkey Oct 13 '16 at 09:28
  • Btw: above code is not working because I forgot to check if `iFirst == iSecond` in which case the program tries to obtain the same lock twice... – Ace7k3 Oct 14 '16 at 08:38

0 Answers0