线程同步之互斥锁--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
中的lock
和unlock
函数来实现线程同步。lock
函数用于锁定互斥量,而unlock
函数用于解锁互斥量。
下面是一个简单的示例,演示了如何在C++11中使用std::mutex
的lock
和unlock
函数:
#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::mutex
的try_lock
、try_lock_for
和try_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_for
和try_lock_until
方法也可以用类似的方式来使用,它们允许线程在一定时间内尝试获取锁,超时则返回false
。
Mutex是一种用于多线程编程的同步原语,用于保护共享资源,以防止多个线程同时访问。在C++中,我们可以使用std::mutex
来实现互斥锁。std::mutex
提供了几种尝试加锁的方法,包括try_lock
、try_lock_for
和try_lock_until
。下面我将分别介绍这三种方法的用法和作用。
try_lock
try_lock
是std::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_for
是std::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_until
是std::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都是用于保护共享资源的锁定机制。它们都是用于确保线程安全和防止数据竞争的重要工具。下面我将解释它们的区别以及在不同情况下应该如何选择使用它们。
-
std::lock_guard:
- std::lock_guard是C++中的一种轻量级锁定机制。
- 它提供了对互斥锁的独占所有权,持续时间由作用域决定。
- 在构造时获取锁,在作用域结束时自动释放锁。
- 不支持手动解锁或重新锁定互斥锁。
- 适用于简单的短期锁定,无需高级功能。
-
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++中用于管理互斥锁的工具,它们之间的区别主要体现在以下几个方面:
-
灵活性:
- lock_guard是一种简单的RAII封装,它在构造时锁定互斥量,在析构时解锁互斥量。它不支持手动解锁或重新锁定互斥量。
- unique_lock提供了更多的灵活性,它可以在构造时锁定互斥量,也可以在后续的代码中手动解锁或重新锁定互斥量。
-
所有权:
- lock_guard一旦被创建,就拥有了互斥量的所有权,直到其作用域结束才会释放。
- unique_lock可以在不同的作用域或线程之间转移互斥量的所有权,也可以在构造时选择是否锁定互斥量。
-
性能:
- 由于unique_lock提供了更多的功能和灵活性,因此在性能上可能会略逊于lock_guard。如果只需要简单的锁定和解锁操作,使用lock_guard可能更为高效。
综上所述,如果需要更多的灵活性、手动控制锁定行为或支持条件变量等高级功能,应该选择unique_lock。而如果只需要简单的独占锁定,并且不需要手动解锁或重新锁定互斥量,可以选择lock_guard。