线程同步
多个线程可以并发执行,但由于资源有限,它们的执行往往是间断性的,进而导致线程以不可预知的速度推进,这就体现了线程的异步性。
异步执行的问题:例如,假设有两个线程A和B,分别负责读取和写入数据。理论上,写入操作应该在读取之前完成。但异步性可能导致读取操作先于写入发生,此时由于缓冲区尚未填充数据,读取线程A将无数据可读并因此被阻塞。
线程同步涉及协调多个并发线程,以确保在执行中按照特定顺序访问共享资源或执行相关的任务。数据读取必须在数据填充之后,这是一个同步过程。
线程互斥
由于线程的并发性,多个线程不可避免地需要同时访问共享资源,如打印机。如果同时发送打印命令,没有适当的管理,打印内容可能会混淆。
这就引入了线程互斥的概念,其目的是确保任何时刻只有一个线程能访问特定的共享资源。在这种情况下,打印机同时只允许一个线程访问,防止数据交叉和资源冲突。当线程 A 在访问打印机时,如果线程 B 也想要访问打印机,它就必须等待,直到 A线程访问结束并释放打印机资源后,B 线程才能去访问。
像上述的打印机这种在一个时间段内只允许一个线程使用的资源,称为临界资源,对临界资源进行访问的那段代码称为临界区。
信号量与 PV 操作
可以通过系统提供的一对原语对信号量进行操作,实现线程的同步互斥
信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量)。
可以用一个信号量来表示系统中某种资源的数量。
eg:比如:系统中只有一台打印机,就可以设置一个初值为1的信号量
这一对原语为:
wait(S) signal(S)原语 S是信号量
wait(S)又称信号量的P操作,使计数器-- P(S)
signal(S)又称信号量的V操作,使计数器++V(S)
信号量的实现
#include <mutex>
#include <condition_variable>
class Semaphore {
private:
std::mutex mutex_;
std::condition_variable condition_;
int count_;
public:
Semaphore(int count = 0) : count_(count) {}
void P() {
std::unique_lock<std::mutex> lock(mutex_);
while (count_ == 0) {
condition_.wait(lock);
}
count_--;
}
void V() {
std::unique_lock<std::mutex> lock(mutex_);
count_++;
condition_.notify_one();
}
};
P 操作和 V 操作必须成对出现。缺少 P 操作就不能保证对临界资源的互斥访问,缺少 V 操作就会导致临界资源永远得不到释放、处于等待态的线程永远得不到唤醒。
实现线程互斥
-
定义一个互斥信号量,并初始化为 1
-
把对于临界资源的访问置于 P 操作和 V 操作之间
#include <iostream>
#include <thread>
Semaphore sem(1);
void task(int n) {
sem.P();
std::cout << "Thread " << n << " 进入临界区." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << n << " 离开临界区." << std::endl;
sem.V();
}
int main() {
// 直接创建每个线程
std::thread t1(task, 1);
std::thread t2(task, 2);
std::thread t3(task, 3);
std::thread t4(task, 4);
std::thread t5(task, 5);
// 分别加入每个线程
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
实现线程同步
-
定义一个同步信号量,并初始化为当前可用资源的数量
-
在优先级较高的操作的后面执行 V 操作,释放资源
-
在优先级较低的操作的前面执行 P 操作,申请占用资源
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class Semaphore {
private:
std::mutex mutex_;
std::condition_variable condition_;
int count_;
public:
Semaphore(int count = 0) : count_(count) {}
void wait() {
std::unique_lock<std::mutex> lock(mutex_);
while (count_ == 0) {
condition_.wait(lock);
}
count_--;
}
void signal() {
std::unique_lock<std::mutex> lock(mutex_);
count_++;
condition_.notify_one();
}
};
Semaphore sem(0);
void funA() {
sem.wait();
std::cout << "执行funA" << std::endl; //
}
void funB() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulate work
std::cout << "执行funB" << std::endl; // Output before sending signal
sem.signal();
}
int main() {
std::thread t1(funA);
std::thread t2(funB);
t1.join();
t2.join();
return 0;
}