线程间通信是多线程编程中的一个重要概念,它指的是在一个程序中的多个线程之间传递信息或者同步执行的过程。在C++中,有多种方式可以实现线程间的通信
1、条件变量
#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> lck(mtx); // 上锁
while (!ready) { // 如果数据未准备好,等待
cv.wait(lck); // 在此等待,直到收到通知
}
// 打印线程ID
std::cout << "thread " << id << '
';
}
void go() {
std::unique_lock<std::mutex> lck(mtx); // 上锁
ready = true; // 设置数据已准备好
cv.notify_all(); // 通知所有等待的线程
}
int main() {
std::thread threads[10];
// 启动10个线程
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 threads ready to race...";
go(); // 开始比赛
for (auto& th : threads) th.join(); // 等待所有线程结束
return 0;
}
在这个示例中,我们创建了10个线程,每个线程都有一个唯一的ID。我们使用一个全局的布尔变量ready来表示数据是否准备好,初始值为false。当数据准备好时,我们将其设置为true,并通过条件变量cv通知所有等待的线程。
每个线程在启动后都会尝试获取互斥锁mtx,然后检查ready的值。如果ready为false,则线程会调用cv.wait(lck)进入等待状态,直到收到通知。当go()函数被调用,将ready设置为true并通知所有等待的线程后,所有线程都会被唤醒,然后打印自己的ID。
这就是一个简单的线程间通信的例子,通过条件变量和互斥锁,我们可以在多个线程之间传递信息和同步执行。
2、共享变量
在C++中,线程间的通信也可以通过共享变量来实现。共享变量是多个线程可以访问的变量,因此它们可以被用来在线程之间传递信息。然而,由于线程可能会同时读写共享变量,因此需要使用互斥锁(mutex
)或其他同步机制来防止数据竞争。
以下是一个简单的示例,其中两个线程共享一个整数变量,并使用互斥锁来保护对它的访问:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥锁.
int shared_var = 0; // 全局共享变量.
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // 获取互斥锁.
++shared_var; // 增加共享变量的值.
mtx.unlock(); // 释放互斥锁.
}
}
int main() {
std::thread t1(increment); // 创建第一个线程.
std::thread t2(increment); // 创建第二个线程.
t1.join(); // 等待第一个线程结束.
t2.join(); // 等待第二个线程结束.
std::cout << "Final value of shared variable: " << shared_var << std::endl; // 输出共享变量的最终值.
return 0;
}
在这个示例中,我们定义了一个全局的互斥锁和一个全局的共享变量。然后,我们创建了两个线程,每个线程都会尝试增加共享变量的值。在每次增加共享变量的值之前,线程都会尝试获取互斥锁。如果互斥锁已经被另一个线程获取,那么这个线程就会等待,直到互斥锁被释放。这样,我们就可以确保在任何时候只有一个线程可以访问共享变量,从而避免了数据竞争。
3、无锁编程
无锁编程是一种并发编程技术,它避免了使用传统的锁机制来同步线程之间的操作。在C++中,可以使用原子操作和内存模型来实现无锁编程。
下面是一个使用C++的std::atomic库实现无锁队列的示例代码:
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
template<typename T>
class LockFreeQueue {
private:
struct Node {
T data;
Node* next;
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
public:
LockFreeQueue() : head(nullptr), tail(nullptr) {}
void enqueue(const T& value) {
Node* newNode = new Node{value, nullptr};
while (true) {
Node* oldTail = tail.load();
Node* oldNext = oldTail->next.load();
if (oldNext != nullptr) {
tail.compare_exchange_strong(oldTail, oldNext);
} else {
if (oldTail->next.compare_exchange_strong(oldNext, newNode)) {
break;
}
}
}
}
bool dequeue(T& result) {
while (true) {
Node* oldHead = head.load();
Node* oldTail = tail.load();
Node* next = oldHead->next.load();
if (oldHead == oldTail) {
if (next == nullptr) {
return false; // 队列为空
}
tail.compare_exchange_strong(oldTail, next);
} else {
result = next->data;
if (head.compare_exchange_strong(oldHead, next)) {
delete oldHead;
return true;
}
}
}
}
};
int main() {
LockFreeQueue<int> queue;
// 生产者线程
std::thread producer([&]() {
for (int i = 0; i < 10; ++i) {
queue.enqueue(i);
std::cout << "Enqueued: " << i << std::endl;
}
});
// 消费者线程
std::thread consumer([&]() {
for (int i = 0; i < 10; ++i) {
int value;
if (queue.dequeue(value)) {
std::cout << "Dequeued: " << value << std::endl;
}
}
});
producer.join();
consumer.join();
return 0;
}
这段代码实现了一个无锁队列,其中使用了std::atomic
来保证原子性操作。LockFreeQueue
类包含了两个std::atomic<Node*>
类型的成员变量head
和tail
,分别表示队列的头部和尾部。enqueue
方法用于将元素入队,dequeue
方法用于从队列中取出元素。
在enqueue
方法中,首先创建一个新的节点newNode
,然后通过循环不断尝试更新队列的尾部指针tail
。如果当前尾部节点的下一个节点不为空,则将尾部指针更新为下一个节点;否则,尝试将新节点设置为当前尾部节点的下一个节点。如果设置成功,则退出循环。
在dequeue
方法中,首先获取队列的头部和尾部指针。如果头部指针等于尾部指针,说明队列为空,返回false
。否则,获取头部指针的下一个节点next
,将其数据赋值给结果变量result
,并尝试将头部指针更新为下一个节点。如果更新成功,则删除旧的头部节点,并返回true
。
在main
函数中,创建了一个生产者线程和一个消费者线程,分别执行入队和出队操作。最后,等待两个线程执行完毕。