Running code on a GUI thread using std::packaged_task
cpp
#include<iostream>
#include<mutex>
#include<deque>
#include<future>
#include<thread>
#include<utility>
std::mutex mtx;
std::deque<std::packaged_task<void()>> tasks;
// 用于指示GUI线程是否应关闭的全局变量
bool g_shutdown = false;
// 判断是否收到关闭消息
bool gui_shutdown_message() {
return g_shutdown;
}
// 处理GUI消息的占位函数
void get_and_process_gui_message() {
// 这里可以模拟处理消息
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
void gui_thread()
{
while (!gui_shutdown_message())
{
get_and_process_gui_message();
std::packaged_task<void()> task;
{
std::lock_guard lk(mtx);
if (tasks.empty()) continue;
task = std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
// 示例:主线程向GUI线程提交任务的函数
template<typename FunctionType>
std::future<void> post_task(FunctionType f)
{
std::packaged_task<void()> task(std::move(f));
std::future<void> res = task.get_future();
{
std::lock_guard lk(mtx);
tasks.push_back(std::move(task));
}
return res;
}
// 示例:主函数,启动GUI线程并提交任务
int main()
{
std::thread gui(gui_thread);
// 提交一个示例任务
auto fut = post_task([] {
std::cout << "Hello from posted task!" << std::endl;
});
fut.get(); // 等待任务完成
// 这里应有机制通知gui_shutdown_message返回true以安全退出
// 可以使用std::this_thread::sleep_for来让程序停顿几秒,例如停顿3秒:
std::this_thread::sleep_for(std::chrono::seconds(3));
g_shutdown = true;
gui.join();
return 0;
}
Making (std::)promises
When you have an application that needs to handle a lot of network connections, it's often tempting to handle each connection on a separate thread, because this can make the network communication easier to think about and easier to program. This works well for low numbers of connections (and thus low numbers of threads). Unfortunately, as the number of connections rises, this becomes less suitable; the large numbers of threads consequently consume large amounts of OS resources and potentially cause a lot of context switching (when the number of threads exceeds the available hardware concurrency), impacting performance. In extreme cases, the OS may run out of resources for running new threads before its capacity for network connections is exhausted. In applications with large numbers of network connections, it's therefore common to have a small number of threads (possibly only one) handling the connections, with each thread dealing with multiple connections at once.
当您的应用程序需要处理大量网络连接时,通常很容易在单独的线程上处理每个连接,因为这会使网络通信更易于考虑和编程。这适用于连接数较少(因此线程数较少)的情况。不幸的是,随着连接数的增加,这变得不太合适;因此,大量线程会消耗大量 OS 资源,并可能导致大量上下文切换(当线程数超过可用的硬件并发时),从而影响性能。在极端情况下,作系统可能会在其网络连接容量耗尽之前耗尽运行新线程的资源。因此,在具有大量网络连接的应用程序中,通常有少量线程(可能只有一个)处理连接,每个线程一次处理多个连接。
Consider one of these threads handling the connections. Data packets will come in
from the various connections being handled in essentially random order, and likewise,
data packets will be queued to be sent in random order. In many cases, other
parts of the application will be waiting either for data to be successfully sent or for a
new batch of data to be successfully received via a specific network connection.
考虑处理连接的这些线程之一。数据包将进来
从基本上以随机顺序处理的各种连接中,同样,
数据包将排队以随机顺序发送。在许多情况下,其他
应用程序的某些部分将等待数据成功发送或
通过特定网络连接成功接收的新一批数据。
Handling single connection from multiple threads using promises
cpp
#include<iostream>
#include<mutex>
#include<queue>
#include<future>
#include<thread>
#include<map>
#include<vector>
// 有效载荷类型
using payload_type = std::vector<char>;
// 数据包
struct data_packet
{
int id; // 数据包ID
payload_type payload; // 数据包内容
};
// 发送数据包
struct outgoing_packet
{
payload_type payload; // 数据包内容
std::promise<bool> promise; // 用于通知发送是否成功
// 默认构造函数
outgoing_packet(const payload_type& p) : payload(p) {}
outgoing_packet(const payload_type& p, std::promise<bool>&& prom) : payload(p), promise(std::move(prom)) {}
// 添加拷贝构造函数
outgoing_packet(const outgoing_packet& other) : payload(other.payload)
{
// 由于 std::promise 不支持拷贝,只能重新初始化
// 这里的逻辑可以根据实际需求调整
promise = std::promise<bool>();
}
// 添加拷贝赋值运算符
outgoing_packet& operator=(const outgoing_packet& other)
{
if (this != &other)
{
payload = other.payload;
// 重新初始化 promise
promise = std::promise<bool>();
}
return *this;
}
};
class Connection
{
private:
int connection_id;
bool connected;
mutable std::mutex mtx;
std::queue<outgoing_packet> outgoing_queue;
std::map<int, std::promise<payload_type>> promises; // 存储未完成的请求
std::queue<data_packet> incoming_queue; // 新增:用于存储接收到的数据包
public:
Connection(int id) : connection_id(id), connected(false) {}
int get_id() const { return connection_id; }
bool is_connected() const { return connected; }
// 检查是否有新数据
bool has_incoming_data() const {
std::lock_guard<std::mutex> lock(mtx);
// 修改 outgoing_packet 类以支持拷贝构造函数
return !incoming_queue.empty();
}
// 检查是否有待发送数据
bool has_outgoing_data() const
{
std::lock_guard<std::mutex> lock(mtx);
return !outgoing_queue.empty();
}
// 获取输入数据
data_packet incoming() {
std::lock_guard<std::mutex> lock(mtx);
if (incoming_queue.empty()) return {0, {}};
data_packet pkt = incoming_queue.front();
incoming_queue.pop();
return pkt;
}
// 获取待发送队列顶部的数据包
outgoing_packet top_of_outgoing_queue() {
std::lock_guard<std::mutex> lock(mtx);
return outgoing_queue.front();
}
// 发送数据
void send(const payload_type& data) {
std::lock_guard<std::mutex> lock(mtx);
// 模拟发送,将数据放入incoming_queue作为回环测试
incoming_queue.push({connection_id, data});
}
// 加入待发送队列
void queue_outgoing(const payload_type& data) {
std::lock_guard<std::mutex> lock(mtx);
outgoing_queue.emplace(data);
}
// 获取与特定ID关联的promise
std::promise<payload_type>& get_promise(int id) {
std::lock_guard<std::mutex> lock(mtx);
return promises[id];
}
// 断开连接
void disconnect() { connected = false; }
// 连接
void connect() { connected = true; }
};
int main() {
// 创建一个连接对象,ID为1
Connection conn(1);
// 连接
conn.connect();
std::cout << "连接状态: " << (conn.is_connected() ? "已连接" : "未连接") << std::endl;
// 构造要发送的数据
payload_type data = {'H', 'e', 'l', 'l', 'o'};
// 发送数据(模拟回环,数据会进入incoming_queue)
conn.send(data);
// 检查是否有接收到的数据
if (conn.has_incoming_data()) {
data_packet pkt = conn.incoming();
std::cout << "收到数据包,ID: " << pkt.id << " 内容: ";
for (char c : pkt.payload) std::cout << c;
std::cout << std::endl;
}
// 加入待发送队列
conn.queue_outgoing({'T', 'e', 's', 't'});
// 检查是否有待发送数据
if (conn.has_outgoing_data()) {
outgoing_packet out_pkt = conn.top_of_outgoing_queue();
std::cout << "待发送数据: ";
for (char c : out_pkt.payload) std::cout << c;
std::cout << std::endl;
}
// 断开连接
conn.disconnect();
std::cout << "连接状态: " << (conn.is_connected() ? "已连接" : "未连接") << std::endl;
return 0;
}
- Connection类的成员变量发送数据包队列和接受数据包队列可能被多个线程同时访问,需要加锁处理。
std::promise
和 std::future
的核心作用
以下是一个简单的C++代码示例,演示了 std::promise
和 std::future
的核心作用:在线程间传递异步结果。
代码示例:异步计算
cpp
#include <iostream>
#include <future>
#include <thread>
// 模拟耗时计算的函数
void compute(std::promise<int> prom) {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(2));
// 设置结果(相当于"生产"数据)
prom.set_value(42); // 将结果存入promise
}
int main() {
// 1. 创建promise对象
std::promise<int> promise;
// 2. 获取与promise关联的future(用于获取结果)
std::future<int> future = promise.get_future();
// 3. 启动异步任务,传递promise
std::thread worker(compute, std::move(promise));
// 主线程可以继续执行其他任务...
std::cout << "主线程继续执行..." << std::endl;
// 4. 等待异步结果(阻塞操作)
int result = future.get(); // 阻塞直到结果就绪
// 5. 使用结果
std::cout << "计算结果: " << result << std::endl;
// 6. 等待工作线程完成
worker.join();
return 0;
}
核心机制解释
-
分工模型:
std::promise
:生产者 ,负责设置结果(通过set_value()
)。std::future
:消费者 ,负责获取结果(通过get()
)。
-
数据流向:
plaintext工作线程 主线程 ─────────────────────────────────── promise.set_value(42) ───> future.get() (设置结果) (获取结果)
-
关键特性:
- 线程安全 :
promise
和future
内部管理共享状态,自动处理同步。 - 一次性事件 :结果一旦设置,
future
永久就绪,多次调用get()
会抛出异常。 - 异常传递 :若工作线程抛出异常,可通过
promise.set_exception()
传递给future
。
- 线程安全 :
扩展用法:多线程协作
cpp
#include <iostream>
#include <future>
#include <thread>
void worker(std::promise<std::string> prom) {
try {
// 模拟可能失败的操作
bool success = true;
if (!success) {
throw std::runtime_error("操作失败");
}
prom.set_value("操作成功");
} catch (...) {
// 捕获异常并传递给future
prom.set_exception(std::current_exception());
}
}
int main() {
std::promise<std::string> prom;
std::future<std::string> fut = prom.get_future();
std::thread([p = std::move(prom)]() mutable {
worker(std::move(p));
}).detach();
try {
// 等待结果或异常
std::string result = fut.get();
std::cout << "结果: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
return 0;
}
总结
- 适用场景 :当需要从一个线程获取另一个线程的执行结果时,使用
promise/future
比手动管理共享状态和条件变量更简洁安全。 - 对比其他同步工具 :
- 相比
std::condition_variable
:无需手动管理互斥锁,专注于结果而非状态。 - 相比
std::async
:更底层灵活,可自定义线程管理。
- 相比
单线程多连接异步处理完整实现
下面是一个完整的单线程多连接异步处理示例。这个示例展示了如何使用 std::promise
和 std::future
管理多个网络连接的异步通信,同时保持单线程事件循环的高效性。
cpp
#include <future>
#include <vector>
#include <queue>
#include <map>
#include <mutex>
#include <thread>
#include <iostream>
#include <string>
#include <chrono>
// 模拟网络数据结构
using payload_type = std::vector<char>;
struct data_packet {
int conn_id; // 连接ID
int request_id; // 请求ID
payload_type payload;
};
struct outgoing_packet {
payload_type payload;
int request_id;
std::promise<bool> promise;
};
// 连接类 - 管理单个网络连接
class connection {
private:
int conn_id;
bool connected;
std::queue<outgoing_packet> outgoing_queue;
std::map<int, std::promise<payload_type>> promises;
std::mutex mtx; // 保护共享资源
// 模拟网络缓冲区
std::queue<data_packet> incoming_buffer;
public:
connection(int id) : conn_id(id), connected(true) {}
// 检查是否有传入数据
bool has_incoming_data() {
std::lock_guard<std::mutex> lock(mtx);
return !incoming_buffer.empty();
}
// 获取传入数据
data_packet incoming() {
std::lock_guard<std::mutex> lock(mtx);
if (incoming_buffer.empty()) {
return {conn_id, -1, {}};
}
auto data = incoming_buffer.front();
incoming_buffer.pop();
return data;
}
// 模拟接收数据 - 实际应用中由网络库调用
void simulate_incoming_data(int request_id, const payload_type& data) {
std::lock_guard<std::mutex> lock(mtx);
incoming_buffer.push({conn_id, request_id, data});
}
// 检查是否有待发送数据
bool has_outgoing_data() {
std::lock_guard<std::mutex> lock(mtx);
return !outgoing_queue.empty();
}
// 获取待发送数据
outgoing_packet top_of_outgoing_queue() {
std::lock_guard<std::mutex> lock(mtx);
return outgoing_queue.front();
}
// 发送数据 - 实际应用中调用网络库
void send(const payload_type& data) {
std::cout << "Connection " << conn_id
<< " sending: " << std::string(data.begin(), data.end()) << std::endl;
// 模拟网络延迟
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// 将数据加入发送队列
void queue_outgoing(const payload_type& data, int request_id) {
std::lock_guard<std::mutex> lock(mtx);
outgoing_queue.push({data, request_id, {}});
}
// 从队列中移除已发送的数据包
void pop_outgoing() {
std::lock_guard<std::mutex> lock(mtx);
if (!outgoing_queue.empty()) {
outgoing_queue.pop();
}
}
// 获取与特定ID关联的promise
std::promise<payload_type>& get_promise(int request_id) {
std::lock_guard<std::mutex> lock(mtx);
return promises[request_id];
}
// 移除已完成的promise
void remove_promise(int request_id) {
std::lock_guard<std::mutex> lock(mtx);
promises.erase(request_id);
}
bool is_connected() const { return connected; }
void disconnect() { connected = false; }
};
using connection_set = std::vector<connection>;
using connection_iterator = connection_set::iterator;
// 检查所有连接是否已断开
bool done(const connection_set& connections) {
for (const auto& conn : connections) {
if (conn.is_connected()) return false;
}
return true;
}
// 主连接处理函数 - 单线程事件循环
void process_connections(connection_set& connections)
{
while(!done(connections))
{
for(auto connection = connections.begin(); connection != connections.end(); ++connection)
{
if (!connection->is_connected()) continue;
// 处理传入数据
if(connection->has_incoming_data())
{
data_packet data = connection->incoming();
try {
auto& p = connection->get_promise(data.request_id);
p.set_value(data.payload);
connection->remove_promise(data.request_id);
std::cout << "Connection " << data.conn_id
<< " received response for request " << data.request_id << std::endl;
} catch (const std::future_error& e) {
std::cerr << "Error setting promise value: " << e.what() << std::endl;
}
}
// 处理传出数据
if(connection->has_outgoing_data())
{
outgoing_packet data = connection->top_of_outgoing_queue();
connection->send(data.payload);
connection->pop_outgoing();
try {
data.promise.set_value(true);
std::cout << "Connection " << connection - connections.begin()
<< " sent request " << data.request_id << std::endl;
} catch (const std::future_error& e) {
std::cerr << "Error setting send promise: " << e.what() << std::endl;
}
}
}
// 避免CPU空转 - 实际应用中可使用更高效的等待机制
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
// 客户端代码示例
int main()
{
// 创建两个连接
connection_set connections;
connections.emplace_back(0); // 连接0
connections.emplace_back(1); // 连接1
// 启动连接处理线程
std::thread processor_thread(process_connections, std::ref(connections));
// 模拟发送请求
auto send_request = [&](int conn_id, const std::string& message, int request_id) {
auto& conn = connections[conn_id];
payload_type data(message.begin(), message.end());
// 获取发送操作的future
outgoing_packet packet{data, request_id, {}};
auto send_future = packet.promise.get_future();
// 将数据包加入发送队列
conn.queue_outgoing(data, request_id);
return send_future;
};
// 模拟接收响应
auto expect_response = [&](int conn_id, int request_id) {
auto& conn = connections[conn_id];
// 创建promise并获取future
std::promise<payload_type> promise;
auto response_future = promise.get_future();
// 存储promise供后续处理
conn.get_promise(request_id) = std::move(promise);
return response_future;
};
// 发送一些请求
int request_id = 100;
auto send_future1 = send_request(0, "Hello from conn0", request_id++);
auto send_future2 = send_request(1, "Hello from conn1", request_id++);
// 预期响应
auto response_future1 = expect_response(0, request_id-2);
auto response_future2 = expect_response(1, request_id-1);
// 模拟服务器响应
std::this_thread::sleep_for(std::chrono::milliseconds(200));
connections[0].simulate_incoming_data(request_id-2, {'O', 'K', '0'});
connections[1].simulate_incoming_data(request_id-1, {'O', 'K', '1'});
// 等待发送完成
bool send_success1 = send_future1.get();
bool send_success2 = send_future2.get();
std::cout << "Send status: conn0=" << (send_success1 ? "OK" : "FAIL")
<< ", conn1=" << (send_success2 ? "OK" : "FAIL") << std::endl;
// 等待响应
if (response_future1.wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
payload_type response = response_future1.get();
std::cout << "Conn0 received: " << std::string(response.begin(), response.end()) << std::endl;
} else {
std::cout << "Conn0 response timeout" << std::endl;
}
if (response_future2.wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
payload_type response = response_future2.get();
std::cout << "Conn1 received: " << std::string(response.begin(), response.end()) << std::endl;
} else {
std::cout << "Conn1 response timeout" << std::endl;
}
// 关闭连接
for (auto& conn : connections) {
conn.disconnect();
}
// 等待处理线程结束
processor_thread.join();
return 0;
}
核心设计要点
-
单线程事件循环
- 主循环遍历所有连接,依次处理每个连接的读写事件
- 使用
has_incoming_data()
和has_outgoing_data()
检查状态 - 通过
sleep_for
避免CPU空转(实际应用可使用更高效的等待机制)
-
异步通信机制
- 发送请求 :将
outgoing_packet
加入队列,关联promise
用于通知发送结果 - 接收响应 :将
promise
与请求ID关联,收到数据后设置promise
值 - 错误处理 :通过异常捕获处理重复设置
promise
的情况
- 发送请求 :将
-
资源管理
- 使用
std::map
关联请求ID和promise
- 请求完成后调用
remove_promise()
清理资源 - 线程安全:使用
std::mutex
保护共享数据结构
- 使用
-
客户端接口
send_request()
:发送数据并返回发送操作的future
expect_response()
:注册预期的响应并返回结果future
- 超时处理:通过
wait_for()
设置响应超时时间
实际应用扩展
-
更复杂的网络库:
- 替换模拟的网络IO为真实的网络库(如Boost.Asio、libcurl)
- 使用非阻塞IO提高效率
-
请求ID管理:
- 添加自动生成唯一请求ID的机制
- 实现请求超时重试逻辑
-
连接管理:
- 添加连接建立和断开的处理
- 实现心跳机制检测连接状态
-
错误处理:
- 处理网络异常并通过
promise.set_exception()
通知 - 实现请求超时自动取消机制
- 处理网络异常并通过
这种单线程多连接的设计模式适用于IO密集型应用,能够高效处理大量并发连接,同时避免多线程带来的复杂性。
当 std::promise
未设置值(即未调用 set_value
/set_exception
)时,调用关联 std::future
的 get()
方法会导致线程阻塞 ,直到 promise
被设置值或异常。若 promise
始终未设置(如被销毁前未操作),则会引发 std::future_error
异常。以下是详细解析:
一、未设置值时的核心行为
1. future.get()
阻塞等待
cpp
#include <future>
#include <thread>
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 未调用 prom.set_value()
std::cout << "即将调用 fut.get()..." << std::endl;
int result = fut.get(); // 永久阻塞!
std::cout << "结果: " << result << std::endl; // 不会执行到这里
}
- 现象 :程序在
fut.get()
处永久阻塞,因为promise
未设置值,future
始终处于未就绪状态。
2. promise
被销毁后的异常
cpp
#include <future>
#include <iostream>
int main() {
{
std::promise<int> prom;
std::future<int> fut = prom.get_future();
} // prom 在此处销毁,未设置值
try {
int result = fut.get(); // 抛出 std::future_error
} catch (const std::future_error& e) {
std::cout << "错误码: " << e.code().value() << std::endl;
std::cout << "错误信息: " << e.what() << std::endl;
// 输出:错误码: 4(std::future_errc::broken_promise)
// 错误信息: broken promise
}
}
- 原因 :
promise
销毁时若未设置值,关联future
进入broken_promise
状态,get()
抛出异常。
二、状态与方法行为表
promise 状态 |
future.wait() |
future.get() |
future.wait_for(time) |
---|---|---|---|
未设置值 | 永久阻塞 | 永久阻塞 | 超时返回 timeout |
已设置值/异常 | 立即返回 | 立即返回结果/抛出异常 | 立即返回 ready |
promise 已销毁未设置 |
抛出 broken_promise |
抛出 broken_promise |
抛出 broken_promise |
三、避免永久阻塞的方法
1. 使用超时等待
cpp
#include <future>
#include <chrono>
#include <iostream>
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 等待1秒
if (fut.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {
std::cout << "等待超时,promise未设置值" << std::endl;
} else {
int result = fut.get();
}
}
2. 异常处理
cpp
#include <future>
#include <iostream>
void set_promise_later(std::promise<int>* prom) {
std::this_thread::sleep_for(std::chrono::seconds(2));
// prom->set_value(42); // 注释此行模拟未设置
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(set_promise_later, &prom);
t.detach();
try {
int result = fut.get(); // 可能永久阻塞或抛出异常
std::cout << "结果: " << result << std::endl;
} catch (const std::future_error& e) {
std::cout << "错误: " << e.what() << std::endl;
} catch (...) {
std::cout << "未知错误" << std::endl;
}
}
四、最佳实践
-
确保
promise
被设置:-
在所有代码路径中调用
set_value
/set_exception
,例如使用try-finally
:cppvoid worker(std::promise<int>& prom) { try { // 计算结果 prom.set_value(42); } catch (...) { prom.set_exception(std::current_exception()); } }
-
-
分离
promise
与future
生命周期:-
避免
future
比promise
先销毁,例如使用std::shared_ptr
管理promise
:cppauto prom = std::make_shared<std::promise<int>>(); std::future<int> fut = prom->get_future();
-
-
使用
std::async
简化操作:-
std::async
自动管理promise
生命周期,避免手动设置错误:cppauto fut = std::async([]{ return 42; }); // 自动设置值 int result = fut.get();
-
五、总结
- 未设置值的风险 :
future.get()
会永久阻塞,若promise
被销毁则抛出broken_promise
异常。 - 解决方案 :
- 始终确保
promise
在销毁前设置值/异常。 - 使用超时等待(
wait_for
)避免永久阻塞。 - 通过异常处理捕获
future_error
。
- 始终确保
promise/future
的设计要求生产者(promise
)与消费者(future
)严格遵循"设置-获取"流程,否则可能导致线程死锁或程序崩溃。