在多线程编程中,线程同步是一种确保多个线程可以安全访问共享资源的技术。它防止了竞态条件、死锁等并发编程中的常见问题。本文将从基础到实践,详细介绍线程同步的五大核心机制:互斥锁(Mutex)、条件变量(Condition Variable)、信号量(Semaphore)、读写锁(Read-Write Lock)和屏障(Barrier)。
1. 互斥锁(Mutex)
互斥锁是最基本的同步机制,用于保证同一时间只有一个线程可以访问共享资源。在大多数编程语言中,互斥锁通常被称为“锁”。
1.1 互斥锁的原理
互斥锁通过维护一个标志位来确保线程的互斥访问。当一个线程试图获取锁时,它会检查标志位。如果标志位为“未锁定”,则线程会将其设置为“锁定”,并继续执行;如果标志位为“锁定”,则线程会等待,直到锁被释放。
1.2 互斥锁的实现
以下是一个使用C++11标准库中的std::mutex实现互斥锁的简单例子:
#include <iostream>
#include <mutex>
std::mutex mtx;
void printHello() {
mtx.lock();
std::cout << "Hello, World!" << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(printHello);
std::thread t2(printHello);
t1.join();
t2.join();
return 0;
}
2. 条件变量(Condition Variable)
条件变量用于线程间的等待和通知。它允许一个或多个线程在某个条件不满足时挂起,直到另一个线程改变条件并通知挂起的线程。
2.1 条件变量的原理
条件变量通常与互斥锁结合使用。当一个线程等待某个条件时,它会释放互斥锁,然后挂起。当条件满足时,另一个线程会通知等待的线程,并重新获取互斥锁。
2.2 条件变量的实现
以下是一个使用C++11标准库中的std::condition_variable实现条件变量的简单例子:
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void waitThread() {
std::unique_lock<std::mutex> lck(mtx);
cv.wait(lck, []{return ready;});
std::cout << "Thread is ready!" << std::endl;
}
void signalThread() {
ready = true;
cv.notify_one();
}
int main() {
std::thread t1(waitThread);
std::thread t2(signalThread);
t1.join();
t2.join();
return 0;
}
3. 信号量(Semaphore)
信号量用于控制对共享资源的访问。它允许一定数量的线程同时访问资源,而其他线程则需要等待。
3.1 信号量的原理
信号量维护一个计数器,表示可用的资源数量。当一个线程请求资源时,它会减少计数器。如果计数器大于0,则线程可以继续执行;否则,线程会等待,直到计数器增加。
3.2 信号量的实现
以下是一个使用C++11标准库中的std::semaphore实现信号量的简单例子:
#include <iostream>
#include <thread>
#include <semaphore>
std::semaphore sem(2);
void task() {
sem.acquire();
std::cout << "Executing task..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task executed." << std::endl;
sem.release();
}
int main() {
std::thread t1(task);
std::thread t2(task);
t1.join();
t2.join();
return 0;
}
4. 读写锁(Read-Write Lock)
读写锁允许多个线程同时读取共享资源,但在写入时需要互斥访问。它提高了并发性能,特别是在读操作远多于写操作的场景中。
4.1 读写锁的原理
读写锁维护两个计数器:一个用于读取计数,另一个用于写入计数。多个线程可以同时增加读取计数,但写入计数只能由一个线程增加。当写入计数增加时,所有读取和写入操作都会被阻塞。
4.2 读写锁的实现
以下是一个使用C++11标准库中的std::shared_mutex和std::mutex实现读写锁的简单例子:
#include <iostream>
#include <mutex>
#include <shared_mutex>
std::shared_mutex rw_mutex;
void read() {
std::shared_lock<std::shared_mutex> lck(rw_mutex);
std::cout << "Reading..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Finished reading." << std::endl;
}
void write() {
std::unique_lock<std::shared_mutex> lck(rw_mutex);
std::cout << "Writing..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Finished writing." << std::endl;
}
int main() {
std::thread t1(read);
std::thread t2(read);
std::thread t3(write);
t1.join();
t2.join();
t3.join();
return 0;
}
5. 屏障(Barrier)
屏障确保所有线程在继续执行之前都到达某个点。屏障常用于并行算法,如并行计算中的归约操作。
5.1 屏障的原理
屏障通过维护一个计数器来跟踪到达屏障的线程数量。当一个线程到达屏障时,它会增加计数器。屏障会等待所有线程到达,然后释放所有线程。
5.2 屏障的实现
以下是一个使用C++11标准库中的std::barrier实现屏障的简单例子:
#include <iostream>
#include <thread>
#include <barrier>
const int num_threads = 4;
std::barrier b(num_threads);
void task() {
std::cout << "Thread " << std::this_thread::get_id() << " reached barrier." << std::endl;
b.wait();
}
int main() {
std::thread t1(task);
std::thread t2(task);
std::thread t3(task);
std::thread t4(task);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
通过以上五大核心机制,我们可以有效地实现线程同步,确保多线程程序的正确性和性能。在实际开发中,应根据具体场景选择合适的同步机制,以达到最佳效果。
