std::lock


Function: std::lock in C++

std::lock is a function template in C++ that provides a way to lock multiple mutex objects simultaneously. It's designed to prevent deadlocks when locking multiple mutexes and is part of the C++ Standard Library's concurrency support.

Key Characteristics

Example 1: Basic Usage with Two Mutexes

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1, mutex2;

void task_a() {
    std::lock(mutex1, mutex2);
    std::cout << "Task A acquired both locks\n";
    mutex1.unlock();
    mutex2.unlock();
}

void task_b() {
    std::lock(mutex2, mutex1);  // Note the different order
    std::cout << "Task B acquired both locks\n";
    mutex2.unlock();
    mutex1.unlock();
}

int main() {
    std::thread t1(task_a);
    std::thread t2(task_b);
    t1.join();
    t2.join();
    return 0;
}

Explanation:

Example 2: Using std::lock with std::unique_lock

#include <iostream>
#include <thread>
#include <mutex>

class Account {
public:
    Account(int balance) : balance_(balance) {}

    void transfer(Account& to, int amount) {
        std::unique_lock<std::mutex> lock_from(mutex_, std::defer_lock);
        std::unique_lock<std::mutex> lock_to(to.mutex_, std::defer_lock);
        std::lock(lock_from, lock_to);

        if (balance_ >= amount) {
            balance_ -= amount;
            to.balance_ += amount;
            std::cout << "Transfer successful\n";
        } else {
            std::cout << "Insufficient funds\n";
        }
    }

private:
    int balance_;
    std::mutex mutex_;
};

int main() {
    Account acc1(1000);
    Account acc2(500);

    std::thread t1(&Account::transfer, &acc1, std::ref(acc2), 300);
    std::thread t2(&Account::transfer, &acc2, std::ref(acc1), 200);

    t1.join();
    t2.join();

    return 0;
}

Explanation:

Example 3: Locking Multiple Mutexes

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx1, mtx2, mtx3, mtx4;

void complex_operation(int id) {
    std::vector<std::mutex*> mutexes = {&mtx1, &mtx2, &mtx3, &mtx4};

    // Shuffle the order of mutexes to simulate different locking orders
    if (id % 2 == 0) {
        std::swap(mutexes[0], mutexes[3]);
        std::swap(mutexes[1], mutexes[2]);
    }

    std::lock(*mutexes[0], *mutexes[1], *mutexes[2], *mutexes[3]);

    std::cout << "Thread " << id << " acquired all locks\n";

    // Unlock in reverse order
    mutexes[3]->unlock();
    mutexes[2]->unlock();
    mutexes[1]->unlock();
    mutexes[0]->unlock();
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(complex_operation, i);
    }

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}

Explanation:

Additional Considerations

  1. Exception Safety: If an exception is thrown while std::lock is acquiring locks, it ensures that any already acquired locks are released.

  2. Performance: While std::lock prevents deadlocks, it may introduce some overhead compared to manually ordering locks.

  3. Alternatives: For simpler cases, consider using std::scoped_lock (C++17) which provides similar functionality with RAII semantics.

  4. Unlock Order: When manually unlocking, it's good practice to unlock in the reverse order of acquisition, though not strictly necessary with std::lock.

  5. Deadlock Prevention: std::lock uses an unspecified algorithm to prevent deadlocks, which may involve a back-off and retry strategy.

Summary

std::lock is a powerful tool in C++ for safely acquiring multiple locks simultaneously, preventing deadlocks in multi-threaded programs. It's particularly useful in complex scenarios where lock ordering is difficult to manage manually or where the lock order may vary. While it provides strong guarantees against deadlocks, it should be used judiciously, considering performance implications in high-contention scenarios. For most cases, especially in modern C++, wrapper classes like std::unique_lock or std::scoped_lock (C++17) provide a more convenient and exception-safe way to use std::lock's functionality. Understanding and correctly using std::lock is crucial for writing robust, deadlock-free multi-threaded C++ programs.

Citations:

[1] https://cplusplus.com/reference/mutex/lock/ [2] https://web.stanford.edu/~ouster/cs111-spring21/lectures/locks/ [3] https://en.cppreference.com/w/cpp/thread/mutex/lock [4] https://stackoverflow.com/questions/18520983/is-stdlock-ill-defined-unimplementable-or-useless [5] https://www.geeksforgeeks.org/std-mutex-in-cpp/

Previous Page | Course Schedule | Course Content