线程同步之互斥锁--mutex


C++中的mutex类

C++中的Mutex类是用于实现线程同步的关键工具之一。它用于保护共享资源,确保在同一时间只有一个线程可以访问共享资源,从而避免数据竞争和不一致性。Mutex类提供了锁定和解锁的功能,确保在访问共享资源时的互斥性。

在C++中,Mutex类通常与std::lock_guard或std::unique_lock等RAII(资源获取即初始化)封装一起使用,以确保在获取锁之后,无论因何原因退出作用域,锁都会被正确释放,从而避免死锁和资源泄漏。

Mutex类的基本功能包括lock()、unlock()和try_lock()等方法,用于获取锁、释放锁和尝试获取锁。此外,Mutex类还可以与条件变量(std::condition_variable)一起使用,实现更复杂的线程同步和通信机制。

总之,C++中的Mutex类是多线程编程中常用的同步原语,用于保护共享资源,确保线程安全的访问和操作。

如何使用Mutex中的lock与unlock

在C++11中,您可以使用std::mutex中的lockunlock函数来实现线程同步。lock函数用于锁定互斥量,而unlock函数用于解锁互斥量。

下面是一个简单的示例,演示了如何在C++11中使用std::mutexlockunlock函数:

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

std::mutex mtx; // 用于临界区的互斥量

void print_thread_id(int id) {
    // 临界区(通过锁定mtx来实现对std::cout的独占访问):
    mtx.lock();
    std::cout << "thread #" << id << '\n';
    mtx.unlock();
}

int main() {
    std::thread threads[10]; // 创建10个线程
    for (int i = 0; i < 10; ++i) {
        threads[i] = std::thread(print_thread_id, i + 1);
    }
    for (auto &th : threads) {
        th.join();
    }
    return 0;
}
/*
thread #1
thread #5
thread #3
thread #4
thread #7
thread #8
thread #9
thread #10
thread #2
thread #6

*/

在这个示例中,我们创建了一个互斥量mtx,然后在print_thread_id函数中使用mtx.lock()来锁定互斥量,以确保对std::cout的独占访问。然后使用mtx.unlock()来解锁互斥量。

这样可以确保在多线程环境中,每个线程都能安全地访问共享资源,避免了竞争条件和数据竞争的问题。

try_lock、try_lock_for和try_lock_until

在C++11中,可以使用std::mutextry_locktry_lock_fortry_lock_until方法来尝试获取互斥锁。这些方法允许线程尝试获取锁,如果获取成功则返回true,否则返回false

以下是使用这些方法的示例代码:

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

std::mutex mtx; // 用于临界区的互斥量

void job() {
    std::cout << "begin thread # " << std::this_thread::get_id() << std::endl;
    if (mtx.try_lock()) {
        std::cout << std::this_thread::get_id() << " Lock acquired" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        mtx.unlock();
    } else {
        std::cout << std::this_thread::get_id() << " Failed to acquired lock" << std::endl;
    }
    std::cout << "end thread # " << std::this_thread::get_id() << std::endl;;
}

int main() {
    std::thread t1(job);
    std::thread t2(job);

    t1.join();
    t2.join();
    
    return 0;
}

/*
begin thread # 139732969322176
139732969322176 Lock acquired
begin thread # 139732960929472
139732960929472 Failed to acquired lock
end thread # 139732960929472
end thread # 139732969322176
*/

在上面的示例中,try_lock方法尝试获取互斥锁,如果获取成功则输出"Lock acquired",否则输出"Failed to acquire lock"。

另外,try_lock_fortry_lock_until方法也可以用类似的方式来使用,它们允许线程在一定时间内尝试获取锁,超时则返回false

Mutex是一种用于多线程编程的同步原语,用于保护共享资源,以防止多个线程同时访问。在C++中,我们可以使用std::mutex来实现互斥锁。std::mutex提供了几种尝试加锁的方法,包括try_locktry_lock_fortry_lock_until。下面我将分别介绍这三种方法的用法和作用。

try_lock
try_lockstd::mutex类的成员函数,用于尝试对互斥锁进行加锁。如果当前没有其他线程占用锁,try_lock会立即对锁进行加锁,并返回true;如果锁已经被其他线程占用,try_lock会立即返回false,而不会阻塞当前线程。

#include <mutex>

std::mutex mtx;

void example_function() {
    if (mtx.try_lock()) {
        // 成功获得锁
        // 执行一些操作
        mtx.unlock();  // 记得在操作结束后释放锁
    } else {
        // 未能获得锁
    }
}

try_lock_for
try_lock_forstd::mutex类的成员函数,用于尝试在指定的时间段内对互斥锁进行加锁。如果在指定时间内获得了锁,函数返回true;如果在指定时间内未获得锁,函数返回false

#include <mutex>
#include <chrono>

std::mutex mtx;

void example_function() {
    if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
        // 成功获得锁
        // 执行一些操作
        mtx.unlock();  // 记得在操作结束后释放锁
    } else {
        // 未能在100毫秒内获得锁
    }
}

try_lock_until
try_lock_untilstd::mutex类的成员函数,用于尝试在指定的时间点之前对互斥锁进行加锁。如果在指定时间点之前获得了锁,函数返回true;如果在指定时间点之前未获得锁,函数返回false

#include <mutex>
#include <chrono>

std::mutex mtx;

void example_function() {
    auto timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
    if (mtx.try_lock_until(timeout)) {
        // 成功获得锁
        // 执行一些操作
        mtx.unlock();  // 记得在操作结束后释放锁
    } else {
        // 未能在指定时间点之前获得锁
    }
}

这些方法可以帮助我们在多线程编程中更灵活地管理互斥锁,避免线程阻塞和死锁的问题。

std::lock_guard和std::unique_lock

在C++中,std::lock_guard和std::unique_lock都是用于保护共享资源的锁定机制。它们都是用于确保线程安全和防止数据竞争的重要工具。下面我将解释它们的区别以及在不同情况下应该如何选择使用它们。

  1. std::lock_guard:

    • std::lock_guard是C++中的一种轻量级锁定机制。
    • 它提供了对互斥锁的独占所有权,持续时间由作用域决定。
    • 在构造时获取锁,在作用域结束时自动释放锁。
    • 不支持手动解锁或重新锁定互斥锁。
    • 适用于简单的短期锁定,无需高级功能。
  2. std::unique_lock:

    • std::unique_lock是一种功能更加丰富的锁定机制,比std::lock_guard具有更多的特性。
    • 它支持对互斥锁的独占所有权和共享所有权。
    • 允许手动解锁、重新锁定以及在不同作用域或线程之间转移所有权。
    • 提供了高级功能,如定时锁定、条件变量和避免死锁。
    • 相对于std::lock_guard,提供了更灵活和可控的锁定行为。

在选择使用std::lock_guard还是std::unique_lock时,需要根据具体的需求来确定:

  • 如果需要简单的独占锁定,且不需要高级功能,可以使用std::lock_guard。
  • 如果需要更多的灵活性、高级功能或手动控制锁定行为,应该选择std::unique_lock。

总之,通过选择合适的锁定机制,可以确保线程安全,防止数据竞争,并构建高效的多线程程序。在编写并发程序时,应根据具体需求选择适当的锁定机制,以确保程序的正确性和效率。

lock_guard的使用示例

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

std::mutex mtx;
int sharedData = 0;

void updateSharedData(int newValue) {
    std::lock_guard<std::mutex> lock(mtx); // 获取互斥锁
    sharedData = newValue; // 更新共享数据
} // 离开作用域时自动释放互斥锁

int main() {
    std::thread t1(updateSharedData, 5); // 启动线程更新共享数据
    std::thread t2(updateSharedData, 10);

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

    std::lock_guard<std::mutex> lock(mtx); // 在主线程中获取互斥锁
    std::cout << "共享数据的值为: " << sharedData << std::endl; // 读取共享数据
    return 0;
}

/*
共享数据的值为: 10
*/

unique_lock的使用示例

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

std::mutex mtx;
std::condition_variable cv;
bool dataReady = false;
int sharedData = 0;

void setDataReady() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        sharedData = 42;
        dataReady = true;
    }
    cv.notify_one(); // 通知等待的线程数据已准备好
}

void processData() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return dataReady; }); // 等待数据准备好的通知
    std::cout << "收到的数据为: " << sharedData << std::endl;
}

int main() {
    std::thread t1(setDataReady); // 启动线程准备数据
    std::thread t2(processData); // 启动线程处理数据

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

    return 0;
}

/*
收到的数据为: 42
*/

unique_lock和lock_guard的区别是什么?
lock_guard和unique_lock都是C++中用于管理互斥锁的工具,它们之间的区别主要体现在以下几个方面:

  1. 灵活性:

    • lock_guard是一种简单的RAII封装,它在构造时锁定互斥量,在析构时解锁互斥量。它不支持手动解锁或重新锁定互斥量。
    • unique_lock提供了更多的灵活性,它可以在构造时锁定互斥量,也可以在后续的代码中手动解锁或重新锁定互斥量。
  2. 所有权:

    • lock_guard一旦被创建,就拥有了互斥量的所有权,直到其作用域结束才会释放。
    • unique_lock可以在不同的作用域或线程之间转移互斥量的所有权,也可以在构造时选择是否锁定互斥量。
  3. 性能:

    • 由于unique_lock提供了更多的功能和灵活性,因此在性能上可能会略逊于lock_guard。如果只需要简单的锁定和解锁操作,使用lock_guard可能更为高效。

综上所述,如果需要更多的灵活性、手动控制锁定行为或支持条件变量等高级功能,应该选择unique_lock。而如果只需要简单的独占锁定,并且不需要手动解锁或重新锁定互斥量,可以选择lock_guard。

posted @ 2024-01-18 22:22  guanyubo  阅读(325)  评论(0编辑  收藏  举报