您好,登录后才能下订单哦!
在多线程编程中,多个线程可能会同时访问共享资源,从而导致数据竞争和不一致的问题。为了解决这些问题,C++提供了互斥锁(Mutex)机制。本文将详细介绍C++中互斥锁的原理及实际使用方法,帮助读者更好地理解和使用互斥锁。
互斥锁(Mutex,全称Mutual Exclusion)是一种同步机制,用于保护共享资源,防止多个线程同时访问。当一个线程持有互斥锁时,其他线程必须等待该线程释放锁后才能访问共享资源。
在多线程环境中,多个线程可能会同时访问和修改共享资源,导致数据竞争(Race Condition)。数据竞争会导致程序行为不可预测,甚至崩溃。互斥锁通过确保同一时间只有一个线程可以访问共享资源,从而避免数据竞争。
C++标准库提供了多种互斥锁类型,以满足不同的需求。
std::mutex
是最基本的互斥锁类型,提供了 lock()
和 unlock()
方法。lock()
方法用于获取锁,unlock()
方法用于释放锁。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block(int n, char c) {
mtx.lock();
for (int i = 0; i < n; ++i) { std::cout << c; }
std::cout << '\n';
mtx.unlock();
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
std::lock_guard
是一个RI(Resource Acquisition Is Initialization)风格的锁管理类,它在构造时自动获取锁,在析构时自动释放锁。使用 std::lock_guard
可以避免忘记释放锁的问题。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block(int n, char c) {
std::lock_guard<std::mutex> lock(mtx);
for (int i = 0; i < n; ++i) { std::cout << c; }
std::cout << '\n';
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
std::unique_lock
是一个更灵活的锁管理类,它允许手动控制锁的获取和释放。std::unique_lock
还支持延迟锁定、尝试锁定和条件变量等功能。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block(int n, char c) {
std::unique_lock<std::mutex> lock(mtx);
for (int i = 0; i < n; ++i) { std::cout << c; }
std::cout << '\n';
lock.unlock(); // 手动释放锁
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
std::recursive_mutex
是一种递归互斥锁,允许同一个线程多次获取锁。这在递归函数中非常有用。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex mtx;
void recursive_function(int n) {
if (n <= 0) return;
std::lock_guard<std::recursive_mutex> lock(mtx);
std::cout << "Thread " << std::this_thread::get_id() << " entered recursive_function with n = " << n << '\n';
recursive_function(n - 1);
}
int main() {
std::thread th1(recursive_function, 3);
std::thread th2(recursive_function, 3);
th1.join();
th2.join();
return 0;
}
std::timed_mutex
是一种带超时功能的互斥锁,允许在指定的时间内尝试获取锁。如果超时仍未获取到锁,则返回 false
。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mtx;
void print_block(int n, char c) {
if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
for (int i = 0; i < n; ++i) { std::cout << c; }
std::cout << '\n';
mtx.unlock();
} else {
std::cout << "Could not acquire lock\n";
}
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
互斥锁的实现依赖于原子操作(Atomic Operation)。原子操作是指在多线程环境中,一个操作要么完全执行,要么完全不执行,不会被其他线程打断。常见的原子操作包括 compare-and-swap
(CAS)和 test-and-set
(TAS)。
自旋锁(Spinlock)是一种简单的互斥锁实现,它通过忙等待(Busy Waiting)的方式等待锁的释放。自旋锁适用于锁持有时间较短的场景,但在锁持有时间较长的情况下,会导致CPU资源的浪费。
#include <atomic>
class Spinlock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// 忙等待
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
现代操作系统提供了对互斥锁的原生支持,如Linux中的 pthread_mutex_t
和Windows中的 CRITICAL_SECTION
。这些操作系统级别的互斥锁实现通常比用户级别的自旋锁更高效,因为它们可以利用操作系统的调度机制来避免忙等待。
互斥锁的基本用法已经在前面介绍过,这里再总结一下:
std::mutex
保护共享资源。std::lock_guard
或 std::unique_lock
自动管理锁的生命周期。lock()
和 unlock()
,以防止忘记释放锁。死锁(Deadlock)是指多个线程互相等待对方释放锁,导致所有线程都无法继续执行。为了避免死锁,可以遵循以下原则:
std::lock
一次性获取多个锁。#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1;
std::mutex mtx2;
void thread1() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Thread 1\n";
}
void thread2() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Thread 2\n";
}
int main() {
std::thread th1(thread1);
std::thread th2(thread2);
th1.join();
th2.join();
return 0;
}
条件变量(Condition Variable)用于在多线程环境中实现线程间的同步。条件变量通常与互斥锁一起使用,以实现复杂的同步逻辑。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "10 threads ready to race...\n";
go();
for (auto& th : threads) {
th.join();
}
return 0;
}
读写锁(Read-Write Lock)允许多个读线程同时访问共享资源,但只允许一个写线程访问共享资源。C++17 引入了 std::shared_mutex
和 std::shared_lock
来实现读写锁。
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex smtx;
int shared_data = 0;
void reader(int id) {
std::shared_lock<std::shared_mutex> lock(smtx);
std::cout << "Reader " << id << " reads " << shared_data << '\n';
}
void writer(int id) {
std::unique_lock<std::shared_mutex> lock(smtx);
++shared_data;
std::cout << "Writer " << id << " writes " << shared_data << '\n';
}
int main() {
std::thread readers[5];
std::thread writers[2];
for (int i = 0; i < 5; ++i) {
readers[i] = std::thread(reader, i);
}
for (int i = 0; i < 2; ++i) {
writers[i] = std::thread(writer, i);
}
for (int i = 0; i < 5; ++i) {
readers[i].join();
}
for (int i = 0; i < 2; ++i) {
writers[i].join();
}
return 0;
}
锁的粒度(Granularity)是指锁保护的资源范围。锁的粒度过大会导致锁竞争加剧,降低并发性能;锁的粒度过小会增加锁的开销。因此,在设计多线程程序时,需要根据实际情况选择合适的锁粒度。
锁的竞争(Lock Contention)是指多个线程同时竞争同一个锁,导致线程等待时间增加。为了减少锁的竞争,可以采用以下策略:
无锁编程(Lock-Free Programming)是一种不使用互斥锁的并发编程技术,通过原子操作和内存屏障来实现线程安全。无锁编程可以避免锁竞争和死锁问题,但实现复杂度较高。
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 100000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::thread th1(increment);
std::thread th2(increment);
th1.join();
th2.join();
std::cout << "Counter = " << counter << '\n';
return 0;
}
死锁是指多个线程互相等待对方释放锁,导致所有线程都无法继续执行。为了避免死锁,可以遵循以下原则:
std::lock
一次性获取多个锁。活锁(Livelock)是指多个线程不断尝试获取锁,但由于某种原因(如优先级反转)导致无法成功获取锁。活锁的解决方案与死锁类似,主要是避免循环等待。
饥饿(Starvation)是指某些线程由于优先级低或竞争激烈,长时间无法获取锁。为了避免饥饿,可以采用公平锁(Fair Lock)或调整线程优先级。
互斥锁是多线程编程中非常重要的同步机制,用于保护共享资源,避免数据竞争。C++标准库提供了多种互斥锁类型,如 std::mutex
、std::lock_guard
、std::unique_lock
等,以满足不同的需求。在实际使用中,需要注意避免死锁、活锁和饥饿等问题,并根据实际情况选择合适的锁粒度和锁类型。通过合理使用互斥锁,可以编写出高效、安全的多线程程序。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。