C++条件变量
C++中的条件变量(Condition Variable)是一种同步原语,用于在多线程程序中阻塞一个或多个线程,直到收到另一个线程的通知。条件变量通常与互斥锁(Mutex)一起使用,以确保在访问共享数据时线程之间的同步。
基本概念
- 互斥锁(Mutex):用于保护共享数据,防止多个线程同时访问造成数据竞争。
- 条件变量(Condition Variable):用于等待某个条件成立。当条件不满足时,线程会阻塞在条件变量上,等待其他线程修改条件并通知(notify)条件变量。
使用条件变量的步骤
- 创建互斥锁和条件变量:在需要同步的共享数据附近创建互斥锁和条件变量。
- 加锁:在访问共享数据或等待条件变量之前,先对互斥锁加锁。
-
等待条件变量:如果条件不满足,则调用条件变量的等待函数(如
wait
、wait_for
、wait_until
),这会使当前线程阻塞并释放互斥锁,直到其他线程调用条件变量的通知函数(notify_one
或notify_all
)唤醒它。 - 被唤醒后重新加锁:当线程被唤醒后,会自动重新对互斥锁加锁,然后再次检查条件是否满足。
- 解锁:在访问完共享数据后,对互斥锁进行解锁。
示例
以下是一个简单的使用条件变量的示例,展示了生产者-消费者模型的基本思想:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
bool ready = false;
void producer(int id) {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lck(mtx);
q.push(i);
std::cout << "Produced " << i << " by producer " << id << std::endl;
ready = true;
cv.notify_one(); // 通知一个等待的线程
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lck(mtx);
while (!ready) cv.wait(lck); // 等待条件变量
// 当我们到达这里时,我们知道条件为真
std::cout << "Consumed " << q.front() << std::endl;
q.pop();
ready = false;
if (q.empty()) break; // 如果队列为空,则退出循环
}
}
int main() {
std::thread producers[10];
std::thread consumer_thread(consumer);
// 启动10个生产者
for (int i = 0; i < 10; ++i)
producers[i] = std::thread(producer, i);
// 等待所有生产者完成
for (auto& th : producers) th.join();
consumer_thread.join();
return 0;
}
注意:这个示例为了简化,ready
变量被用作一个简单的条件标志。在实际应用中,你可能需要更复杂的条件逻辑来确保线程间的正确同步。此外,示例中的consumer
函数使用了while
循环来检查条件,这是因为在wait
函数返回后,条件可能已经被其他线程改变了,所以需要重新检查条件是否仍然满足。
线程同步和异步
线程同步(Synchronous Threads)和异步(Asynchronous Threads)是并发编程中的两个重要概念,它们在处理线程间的任务执行顺序和数据共享方面存在显著差异。
线程同步
定义:线程同步是指多个线程在执行过程中,需要按照一定的顺序或规则来访问共享资源或执行特定操作,以确保数据的一致性和程序的正确性。
特点:
- 顺序执行:一个线程必须等待另一个线程完成其任务后才能继续执行。这保证了程序的执行顺序和数据的一致性。
- 数据一致性:同步线程能够确保在多个线程同时访问共享数据时,数据的一致性和完整性。
- 易于编程和维护:同步线程的执行流程相对简单,容易理解和实现,也有利于代码的维护和调试。
- 适用场景:适用于需要按照特定顺序执行任务、保证数据一致性的场景,如银行转账系统中的转账操作。
实现方式:同步可以通过多种机制来实现,如互斥锁(Mutex)、信号量(Semaphore)、事件(Event)等。这些机制用于控制线程对共享资源的访问,确保同一时刻只有一个线程可以访问该资源。
线程异步
定义:线程异步是指一个线程在执行任务时,不需要等待该任务完成就可以继续执行其他任务。这种方式允许线程并行处理任务,从而提高程序的执行效率。
特点:
- 并行执行:异步线程允许线程并行处理任务,从而加快程序的执行速度。
- 减少等待时间:当一个线程需要等待某个资源或完成某个长时间运行的任务时,异步线程可以让其他线程继续执行,从而减少用户等待时间。
- 充分利用多核资源:异步线程可以充分利用多核处理器的计算能力,提高程序的并行处理能力。
- 复杂性增加:异步编程通常比同步编程更复杂,需要处理回调、Promise、事件等,容易导致代码难以理解和维护。
- 调试困难:异步操作的非线性执行顺序使得调试和追踪问题变得更加困难。
适用场景:异步线程适用于需要并行处理大量任务、提高程序执行效率的场景,如网络编程、文件IO操作、复杂计算等。
C11的同步和异步
在C++中,线程同步和异步是处理多线程程序时需要考虑的重要方面。C++标准库从C++11开始引入了多线程支持,包括线程(std::thread
)、互斥锁(std::mutex
)、条件变量(std::condition_variable
)、锁保护器(如std::lock_guard
和std::unique_lock
)等,用于实现线程的同步和异步。
线程同步
在C++中,线程同步通常涉及使用互斥锁、条件变量或其他同步机制来确保多个线程在访问共享资源时不会出现数据竞争或条件竞争。例如:
-
互斥锁(
std::mutex
):用于保护共享数据,确保同一时间只有一个线程可以访问该数据。 -
条件变量(
std::condition_variable
):与互斥锁一起使用,允许线程在特定条件未满足时挂起,并在条件满足时被其他线程唤醒。 -
锁保护器(
std::lock_guard
、std::unique_lock
):这些RAII(Resource Acquisition Is Initialization)风格的封装器可以自动管理互斥锁的加锁和解锁,减少死锁的风险。
线程异步
在C++中,实现线程异步通常意味着启动一个或多个线程来并行执行任务,而这些任务的执行不会阻塞主线程或调用线程。例如:
-
std::thread
:用于表示一个独立的执行线程。你可以创建一个std::thread
对象来启动一个新线程,并在其中执行指定的函数或可调用对象。 -
异步操作(如
std::async
):从C++11开始,std::async
函数提供了一种启动异步任务的方法。它返回一个std::future
对象,该对象可以用来获取异步操作的结果。std::async
可以自动管理线程的创建和销毁,以及结果的存储和检索。 -
并发算法和容器:C++标准库还提供了一些并发算法和容器(如
std::vector
的并行算法版本),它们可以在多个线程上并行执行操作,从而提高程序的执行效率。
C++I/o操作
在C++中,I/O(输入/输出)操作是程序与外部世界交互的基本方式。C++标准库提供了丰富的I/O库来支持文件、控制台(命令行)以及其他输入输出设备的数据交换。以下是一些主要的C++ I/O操作及其相关类的概述:
标准输入输出流
-
std::cin
:用于从标准输入(通常是键盘)读取数据。 -
std::cout
:用于向标准输出(通常是屏幕)输出数据。 -
std::cerr
:用于向标准错误输出流输出错误信息,通常不经过缓冲,直接输出。 -
std::clog
:类似于std::cerr
,但它会经过缓冲处理。
这些流都继承自std::ostream
(对于输出)或std::istream
(对于输入)类,并提供了丰富的成员函数和操作符重载来支持格式化输入输出。
文件输入输出
C++通过std::ifstream
(用于从文件读取数据)、std::ofstream
(用于向文件写入数据)和std::fstream
(同时支持读写)类来支持文件I/O。这些类都是模板类,但它们通常与char
类型一起使用,分别对应std::ifstream<char>
、std::ofstream<char>
和std::fstream<char>
。
#include <fstream>
#include <iostream>
int main() {
std::ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "Hello, file!";
outFile.close();
}
std::ifstream inFile("example.txt");
std::string line;
if (inFile.is_open()) {
while (getline(inFile, line)) {
std::cout << line << '\n';
}
inFile.close();
}
return 0;
}
字符串流
C++还提供了std::istringstream
、std::ostringstream
和std::stringstream
类,用于在字符串上进行输入输出操作。这些类非常有用,特别是当你需要在内存中进行数据转换或分割时。
#include <sstream>
#include <iostream>
#include <string>
int main() {
std::string data = "123 456 789";
std::istringstream iss(data);
int num;
while (iss >> num) {
std::cout << num << '\n';
}
std::ostringstream oss;
oss << "The answer is " << 42;
std::string answer = oss.str();
std::cout << answer << '\n';
return 0;
}
格式化输入输出
C++标准库中的I/O流支持通过iomanip
库中的操作符和函数来格式化输出。例如,你可以设置浮点数的小数点后的位数、控制整数的基数(十进制、十六进制等)、填充字符等。
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::fixed << std::setprecision(2) << 3.14159 << '\n'; // 输出: 3.14
std::cout << std::hex << 255 << '\n'; // 输出: ff
return 0;
}
缓冲区管理
所有的I/O流都使用缓冲区来管理数据的输入输出。你可以通过成员函数如flush()
、sync()
和pubsync()
来管理缓冲区,确保数据被及时写出或读入。