说说【C++高阶系列】异步任务的结果怎么回来?一文理解 future、async、promise、shared_future 与 packaged_task

前段时间遇到一个小问题,后来发现这是个挺常见的坑,顺手整理一篇笔记。

🔥 这篇文章专栏:C++高阶 🌸作者主页:努力努力再努力wz
💪 今日博客励志语录:能力不是某一天突然拥有的,它只是无数个“我今天再搞懂一点”叠加出来的。

思维导图

单线程顺序执行
        ↓
某个函数执行时间很长
        ↓
后续逻辑并不依赖当前函数结果
        ↓
没有必要让当前执行流一直等待
        ↓
把耗时任务交给另一个执行流
        ↓
main 线程继续处理其他逻辑
        ↓
新的问题出现:
另一个执行流的结果怎么传回来?
        ↓
传统方案
        ↓
thread
+
共享结果 result
+
完成状态 ready
+
mutex
+
condition_variable
        ↓
可以解决问题
但“任务执行 + 结果同步”需要程序员手动组织
        ↓
C++11  提供更高层的异步结果抽象
        ↓
                shared state
               /            \
              /              \
        Producer            Consumer
              |              |
   async / promise /     future /
   packaged_task         shared_future
        ↓
std::async
        ↓
“任务执行 + 结果写入 + future”整体封装
        ↓
std::promise
        ↓
任务如何执行由程序员控制
只把结果写入 shared state 的能力暴露出来
        ↓
std::packaged_task
        ↓
进一步把
“执行 callable + 捕获结果/异常 + 写 shared state”
封装成一个任务对象
        ↓
线程池
        ↓
任务去程:
提交线程 → packaged_task → Task Queue → Worker

结果回程:
Worker → shared state → future → 提交线程


引入

在学习 std::future、std::async、std::promise、std::shared_future 和 std::packaged_task 之前,如果直接从这些 API 的函数声明开始:

std::async(...)
future.get()
promise.set_value(...)
future.share()
packaged_task(...)

很容易出现一个问题:

每一个接口似乎都能看懂,但是不清楚这些东西为什么会被设计出来,也不清楚它们和咱们此前学习的 std::thread、互斥锁以及条件变量之间到底是什么关系。

于是这里并不准备直接从 API 列表开始,而是先回到一个最基本的并发场景。

假设当前只有一个线程,其中需要依次完成多个动作:

Task A
  ↓
Task B
  ↓
Task C
  ↓
Task D

单线程执行流天然具有顺序性。

也就是说:

A 没执行结束
    ↓
B 就不能开始

B 没执行结束
    ↓
C 就不能开始

如果这些动作都很快,这并没有什么问题。

但是如果其中:

Task A

是一个十分耗时的操作,而后面的:

Task B
Task C
Task D

又完全不依赖 Task A 的执行结果,那么让当前线程一直停留在 Task A 上等待,就会产生明显的执行流浪费。

于是整个问题就自然演变成:

能不能把这个耗时任务交给其他执行流处理,而当前线程继续完成后续逻辑,等真正需要结果时再回来获取?

这就是理解 C++ future 体系最合适的切入点。


一、先从同步执行开始

假设存在一个耗时函数:

int calculate()
{
    // 假设内部执行时间很长
    return 100;
}

最普通的调用方式:

int result = calculate();

doSomething();

整个执行流就是:

main 线程
    |
    | calculate()
    |
    | 执行耗时计算
    |
    | return 100
    |
    v
doSomething()

这里 calculate() 没有返回之前:

main

不会继续执行后面的 doSomething()。

在当前语境下,可以将这种行为理解为:

同步调用

也就是:

当前执行流发起一个动作以后,要等这个动作完成并得到结果,才能继续向后推进。

如果:

calculate() = 5 秒

那么 main 线程就会在这里停留大约 5 秒。


二、当后续逻辑不依赖当前结果时,可以把任务拆出去

现在假设:

calculate()

虽然需要 5 秒,但是 main 后面要做的事情:

prepareData();
printLog();
handleOtherWork();

完全不依赖 calculate() 的结果。

那么更合理的执行结构就是:

calculate()
                   /
main 线程 --------+
   |               \
   |                其他执行流继续计算
   |
   | prepareData()
   |
   | printLog()
   |
   | handleOtherWork()
   |
   | 真正需要结果
   v
获取 calculate() 的结果

这里就从:

同步

过渡到了:

异步

可以先建立一个简单认知:

异步并不是“任务不用执行了”,而是任务不再要求沿着当前执行流同步完成。

当前线程负责:

发起任务

而任务真正的执行可以由其他执行流完成。

但是新的问题马上出现:

任务虽然交给别人执行了,结果最终怎么回来?

这才是整个 future 体系真正要解决的问题。


三、没有 future 以前,咱们完全可以自己解决

在没有使用 future 这些高层抽象以前,最直接的办法就是:

std::thread
+
共享资源
+
mutex
+
condition_variable

例如:

#include
#include
#include
#include

int calculate()
{
    return 100;
}

int main()
{
    int result = 0;
    bool ready = false;

    std::mutex mtx;
    std::condition_variable cv;

    std::thread worker([&]() {
        int temp = calculate();

        {
            std::lock_guard lock(mtx);

            result = temp;
            ready = true;
        }

        cv.notify_one();
    });

    // main 可以继续执行其他逻辑
    doSomething();

    {
        std::unique_lock lock(mtx);

        cv.wait(lock, [&]() {
            return ready;
        });

        std::cout  我们实际上是在自己搭建一条“异步结果通道”。

---

## 四、future 并不是一种全新的并发原理

初次接触 `future` 时很容易形成一个误区:


thread + mutex + condition_variable
↓
旧方案

future + async
↓
一种全新的、更先进、更高性能的机制


这种理解并不准确。

`future` 体系解决的仍然是:


任务异步执行
+
结果如何跨执行流传递
+
消费者如何等待任务完成

区别主要在于抽象层次。

原来:


std::thread
共享结果
ready
mutex
condition_variable
异常处理
生命周期

都需要程序员自己组织。

而现在:


std::async
+
std::future

把其中大量同步细节封装起来。

于是程序员面对的接口变成:


提交任务
↓
拿到 future
↓
继续执行其他干活
↓
future.get()

因此需要建立一个非常重要的认识:

> future 的主要价值是提供统一、简单的“异步结果”抽象,而不是天然比我们自己编写线程同步代码更快。

---

## 五、如果只看性能,future 也不是所谓的“性能神器”

如果两种方案最终都需要为一个任务建立异步执行流,那么真正比较重的成本往往来自:


线程创建
线程调度
上下文切换
任务本身
同步等待

而不是单纯:


future 这个 C++ 对象

`future` 体系为了提供通用能力,还需要维护:


shared state
结果
异常
完成状态
生命周期
等待机制

因此不能简单认为:


future
一定比
mutex + condition_variable
更快

反过来,如果我们针对一个非常确定的场景自己设计:


固定线程
+
特定数组 / RingBuffer
+
原子索引
+
特定同步策略

理论上的性能上限往往可以更高,因为我们不需要承担通用抽象中所有功能的成本。

但是:

> “自己控制”并不等于“自己写的一定更快”。

如果自己设计时出现:


锁粒度过大
频繁竞争
false sharing
频繁唤醒
缓存局部性差
大量上下文切换

最终完全可能比标准库实现更慢。

因此真正的性能思路应该是:


先建立正确结构
↓
profiling
↓
确定真正热点
↓
再决定是否下沉到自定义同步

对于一个本身执行几百毫秒甚至几秒的耗时任务来说,`future` 带来的一点管理开销通常根本不是主要矛盾。

而对于每秒几十万次、每个任务只有几微秒的高频任务来说,真正应该考虑的也往往不是:


future
vs
condition_variable

而是:


线程池
任务队列
批处理
减少线程创建
减少唤醒
减少上下文切换
缓存局部性
原子与无锁结构

---

## 六、整个 future 体系真正的核心:shared state

现在开始进入 `future` 的核心。

假设我们写:


std::future f =
std::async(std::launch::async, calculate);

这里最重要的并不是:


future 对象本身存着 result

而是标准库会维护一个:


shared state

可以把它理解成一块由标准库管理的:

> 异步结果控制块。

逻辑上大致包含:


+----------------------------+
| shared state |
| |
| ready / not ready |
| |
| result |
| |
| exception |
| |
| synchronization state |
| |
+----------------------------+

需要注意:

> 这只是心智模型,C++ 标准并没有规定实现必须真的存在这些字段,也没有规定内部必须使用 mutex + condition_variable。

标准规定的是行为语义,而不是具体内部实现。

---

### 1. shared state 是生产者和消费者之间的桥梁

整个结构可以画成:


Producer Consumer
| |
| |
v v
+--------------------+
| shared state |
| |
| result / exception |
| ready state |
+--------------------+

生产者负责:


产生结果
↓
写入 shared state

消费者负责:


等待 shared state
↓
获取结果

因此后面学习:


async
promise
packaged_task

时,实际上都可以问同一个问题:

> 谁是 shared state 的生产者端?

而:


future
shared_future

则都是消费者端。

---

### 2. future 不是 signal 那种“主动通知”

这里还需要区分 Linux 中比较熟悉的信号机制。

Linux signal 更接近:


事件发生
↓
内核记录信号
↓
目标执行流后续处理信号

而 `future` 并不是:


任务完成
↓
future 主动调用一个回调函数通知 main

它更接近:


任务完成
↓
shared state 变成 ready
↓
消费者通过 future
进行 wait / get / timed wait

所以:


signal

偏向事件通知。

而:


future

更偏向:


等待
+
同步
+
结果传递
+
异常传播

---

## 七、std::future 到底是什么

`std::future` 是定义在:


include



中的类模板。

可以先粗略理解为:


template
class future;

这里:


T

表示:

> 异步操作正常完成以后产生的“值结果类型”。

例如:


int calculate()
{
return 100;
}

对应:


std::future

而:


std::string getName()
{
return "wangz";
}

对应:


std::future

整个类型关系就是:


callable(args...)
↓
最终结果类型 T
↓
std::future

---

### 1. future

如果任务没有返回值:


void saveFile()
{
// ...
}

仍然可以:


std::future f =
std::async(std::launch::async, saveFile);

这里并不是:


shared state 里保存了一个 void 对象

而是:

> 虽然没有值结果需要返回,但是消费者仍然关心任务什么时候完成,以及任务是否抛出了异常。

所以:


f.get();

依然有意义。

它表达:


等待任务完成
+
如果存在异常则重新抛出

---

## 八、future 更像一个“异步结果句柄”

可以把:


std::future f;

理解成:


future f
|
| 关联
v
shared state

`future` 本身不是那个 shared state。

更加准确地说:

> future 是消费者访问 shared state 的句柄。

类似我们理解文件描述符时:


fd
|
v
内核对象

这里也可以形成:


future
|
v
shared state

当然,这只是抽象类比,并不是说 `future` 内部真的保存了一个整数文件描述符。

---

## 九、future 为什么不能复制,而且 get() 只能调用一次

普通:


std::future

采用的是:


单消费者
+
一次性消费

语义。

这点可以借助 `std::unique_ptr` 来理解。

假设异步任务返回:


std::unique_ptr create()
{
return std::make_unique(100);
}

那么:


std::future f =
std::async(std::launch::async, create);

shared state 最终承载的是:


unique_ptr

当:


auto ptr = f.get();

发生时,结果需要交给唯一消费者。

概念上:


shared state
|
| move result
v
future.get()
|
v
ptr

第一次结果已经被拿走以后,就不存在第二个相同的独占资源可以继续拿。

为了让 `future<T>` 对各种 `T` 都具有统一语义,标准并没有设计:


future
可以 get 多次

future
只能 get 一次


而是统一采用:

> 一个普通 future 对应一次结果消费。

因此:


auto value = f.get();

以后:


f.valid() == false;

这里更准确的理解是:


get() 之前:

future f
|
v
shared state

get() 之后:

future f
X

不再关联原来的 shared state


可以把它心智模型化成:


内部关联被置空

但这依然只是模型,并不代表实现内部一定真的有一根裸指针被赋值为 `nullptr`。

---

### 1. 为什么只能 move,不能 copy

如果允许:


std::future f2 = f1;

就会出现:


shared state
/ \
/ \
f1 f2

那么:


谁拥有最终结果的独占消费权?

尤其当结果是:


std::unique_ptr

时,这个问题更加明显。

因此普通 `future`:


不能 copy
可以 move

移动表达的不是:


复制一张结果凭证

而是:


把消费权转移给另外一个 future

例如:


std::future f2 = std::move(f1);

之后:


f1
X

f2
|
v
shared state


这和:


std::unique_ptr

在“独占语义”上非常相似。

---

## 十、std::async:把任务执行和 future 结果通道一起封装

有了 `future` 以后,再看:


std::async

就很自然了。

`std::async` 本身是函数模板,可以先把常用形式理解成:


std::async(
policy,
callable,
args...
);

其中分别表示:


policy
↓
怎么执行任务

callable
↓
执行什么任务

args...
↓
给任务传什么参数


例如:


int add(int a, int b)
{
return a + b;
}

auto f = std::async(
std::launch::async,
add,
10,
20
);


可以直接翻译成人话:


按照 launch::async 策略
执行 add(10, 20)

由于:


add(10, 20)

返回:


int

所以:


std::async(...)

返回:


std::future

即:


callable(args...)
↓
结果类型
↓
future

在 C++17 及之后的标准描述中,这个结果类型可以用 `std::invoke_result_t` 来表达。

---

## 十一、std::launch 是什么

`std::launch` 是一个作用域枚举类型,同时具备 BitmaskType 语义,用于描述 `std::async` 的启动策略。

我们当前最需要认识两个值:


std::launch::async
std::launch::deferred

---

### 1. launch::async


std::async(
std::launch::async,
func
);

表示:

> 任务在与调用方不同的线程执行流中异步执行。

可以建立:


main async execution
| |
| std::async() |
|------------------------------->|
| |
| func()
| |
v |
继续执行 |

---

### 2. launch::deferred


std::async(
std::launch::deferred,
func
);

采用的是延迟执行。

也就是:


调用 async
↓
暂时不执行 func
↓
保存任务
↓
第一次通过非定时等待真正请求结果
↓
在执行等待操作的线程中执行 func

例如:


auto f = std::async(
std::launch::deferred,
calculate
);

f.get();


`calculate()` 可能直到:


f.get();

时才真正执行。

---

### 3. 不显式指定 policy

如果直接:


std::async(func);

默认语义相当于允许:


launch::async
|
launch::deferred

由实现选择具体策略。

所以在学习阶段,为了让执行模型足够明确,可以优先写:


std::async(
std::launch::async,
func
);

---

## 十二、async 背后真正封装了什么

假设:


int calculate()
{
return 100;
}

然后:


auto f =
std::async(
std::launch::async,
calculate
);

从心智模型上,可以把异步执行逻辑理解成:


void async_wrapper()
{
try
{
int result = calculate();

// 写入 shared state
store_result(result);

// 标记 ready
mark_ready();
}
catch (...)
{
// 保存异常
store_exception(std::current_exception());

// 标记 ready
mark_ready();
}
}


这当然不是标准库源码,只是在描述语义。

也就是说:


std::async
↓
安排异步任务
↓
标准库包装逻辑
↓
调用用户 callable
↓
+----------------+
| |
return throw
| |
v v
保存 result 保存 exception
| |
+-------+--------+
|
v
shared state ready
|
v
future

所以:

> 异步执行流真正执行的不只是用户函数本体,还包括标准库围绕结果和异常建立的包装逻辑。

---

## 十三、future 最核心的接口

`std::future<T>` 的主要接口都围绕:


“我如何观察、等待和消费 shared state?”

展开。

最常见的有:


valid()
wait()
wait_for()
wait_until()
get()
share()

---

### 1. get():等待 + 消费结果


int result = f.get();

可以直接理解成:


结果 ready 了吗?
|
+--+--+
| |
否 是
| |
等待 |
| |
+--+--+
|
v
获取结果

所以:


get()
≈
wait until ready
+
consume result

如果 shared state 保存的是异常:


future.get()
↓
重新抛出该异常

因此 `future` 还承担了跨异步执行边界传播异常的作用。

---

### 2. wait():只等待,不消费结果


f.wait();

只表示:


等到 shared state ready

不会把结果取出来。

因此可以:


f.wait();

int result = f.get();


此时:


wait()
= 只等待

get()
= 等待 + 消费


---

### 3. valid():判断是否仍然关联 shared state


f.valid();

非常容易被误解成:


任务完成了吗?

实际上它问的是:

> 当前 future 是否仍然关联某个 shared state?

所以:


valid
≠
ready

例如异步任务还没完成:


shared state
not ready

但是:


future
|
v
shared state

关联还存在,那么:


f.valid() == true;

而调用:


f.get();

完成一次消费以后:


f.valid() == false;

---

### 4. wait_for():等待一段相对时间

例如:


auto status =
f.wait_for(std::chrono::seconds(2));

表示:


从现在开始
最多等待 2 秒

结果使用:


std::future_status

描述,常见情况包括:


std::future_status::ready
std::future_status::timeout
std::future_status::deferred

即:


ready
↓
结果准备完成

timeout
↓
等待时间耗尽

deferred
↓
关联的是延迟执行任务


---

### 5. wait_until():等待到某个时间点

`wait_for()`:


再等 2 秒

属于:


相对时间

而:


f.wait_until(time_point);

表示:


等到某个具体时间点

属于:


绝对时间点

---

### 6. share():把独占 future 转换为 shared_future


auto sf = f.share();

表示:


future f
|
v
shared state

转换为:


shared_future sf
|
v
shared state

而原来的:


f

会失去关联:


f.valid() == false;

---

## 十四、一个完整的 async + future 示例


include


include


include


include

int calculate(int a, int b)
{
std::cout worker
packaged_task

结果流

提交线程 Task Queue 负责“任务怎么过去”,shared state + future 负责“结果怎么回来”。

可以记成:

任务去程
    ↓
Task Queue

结果回程
    ↓
shared state + future


三十四、为什么 packaged_task 在线程池里比手动 promise 更自然

如果在线程池中使用 promise:

worker
   ↓
调用任务函数
   ↓
得到 result
   ↓
promise.set_value(result)

发生异常
   ↓
promise.set_exception(...)

worker 一定要参与:

结果类型
返回值
异常处理
结果写入

这些逻辑。

而如果使用:

packaged_task

worker 只需要:

task();

它不需要关心:

任务返回 int?
任务返回 string?
任务返回 unique_ptr?
任务是不是 void?
任务有没有抛异常?
结果最后交给谁?

这些都已经由:

packaged_task
+
shared state
+
future

绑定起来。

这就是它在线程池中的价值。


三十五、不同返回类型的任务怎么放进同一个队列

实际线程池中,用户提交的任务可能是:

int task1();
void task2();
std::string task3();

对应的:

packaged_task
packaged_task
packaged_task

类型都不同。

但是任务队列通常希望保存一种统一类型,例如:

std::function

一个经典思路是:

auto task =
    std::make_shared<
        std::packaged_task
    >(
        [] {
            return calculate();
        }
    );

std::future future =
    task->get_future();

queue.push(
    [task]() {
        (*task)();
    }
);

这样:

原始任务
    ↓
packaged_task
    ↓
包装成 void() callable
    ↓
统一进入 std::function 队列

worker 永远只需要:

job();

而真正的返回值仍然会自动进入对应的 shared state。


三十六、future / promise / packaged_task 的 shared state 关系

现在可以把整个体系压缩成一张图:

shared state
                      /            \
                     /              \
              Producer             Consumer
                  |                   |
        +---------+---------+         |
        |         |         |         |
      async    promise  packaged_task |
        |         |         |         |
        +---------+---------+         |
                  |                   |
                  +------------+------+
                               |
                         future
                            /
                           /
                   shared_future

更加准确地说:

async
    ↓
标准库帮你执行 callable
并自动完成 shared state

promise
    ↓
你自己产生结果
再手动 set_value / set_exception

packaged_task
    ↓
你决定何时/在哪里执行 task
task 自动执行 callable
并自动完成 shared state

消费者侧:

future
    ↓
独占式、一次性消费

shared_future
    ↓
共享式、多消费者读取


三十七、把整个体系放回最开始的传统方案

最开始:

std::thread
+
result
+
ready
+
mutex
+
condition_variable

咱们自己控制:

任务执行
结果存储
完成状态
加锁
等待
唤醒
异常处理
生命周期

而 future 体系则是在不同位置提供不同抽象。


1. async + future

我只想说:

“执行这个任务,
以后把结果给我。”

于是:

任务执行
结果捕获
异常捕获
shared state
future

基本整体封装。


2. promise + future

线程怎么创建
任务怎么执行
结果什么时候产生

全部自己控制。

但是:

结果怎么安全交给消费者
消费者怎么等待
异常怎么传播

交给标准库。


3. packaged_task + future

进一步在 promise 的基础上把:

callable
    ↓
返回值 / 异常
    ↓
shared state

这段封装起来。

程序员只控制:

什么时候执行 task
在哪个执行流执行 task


4. 自定义同步

如果追求更强控制能力,可以继续下沉:

自定义 Task Queue
自定义共享结构
atomic
mutex
shared_mutex
condition_variable
semaphore
RingBuffer
线程池
绑核
缓存布局

但是代价就是:

正确性、生命周期和同步细节全部重新由程序员自己承担。

三十八、最终理应怎么选择

这里不能形成:

新特性出现
    ↓
旧方案淘汰
    ↓
以后全部使用 future

这种思维。

正确方式理应是:

先看需求
    ↓
需要多少控制权
    ↓
是否真的处于性能热点
    ↓
选择合适抽象层级

如果只是:

偶尔执行一个真正耗时的独立任务

可以优先考虑:

async + future

因为代码和语义十分直接。

如果:

线程我自己管理
结果产生过程比较复杂

可以使用:

promise + future

如果:

任务本身需要进入队列
未来由 worker 执行
并且提交者需要 future

那么:

packaged_task + future

十分自然。

如果:

一个结果需要多个消费者

则需要:

shared_future

而如果场景已经进入:

高频微任务
超低延迟
固定 worker
自定义调度
无锁队列
CPU affinity
缓存局部性优化

那么真正理应研究的就是:

线程池
Task System
Atomic
Lock-Free
Work Stealing
Batching

而不是简单纠结:

future
vs
condition_variable


三十九、最终心智模型

到这里,可以把这一整套 C++11 异步结果机制压缩成下面这张图。

“我要一个异步结果”
                            |
                            v
                      shared state
                     /            \
                    /              \
              Producer           Consumer
                  |                  |
       +----------+----------+       |
       |          |          |       |
     async     promise   packaged_task
       |          |          |
       |          |          |
       |          |          +--> callable + result channel
       |          |
       |          +--> 手动 set_value / set_exception
       |
       +--> 任务执行到结果通道整体封装

                                 Consumer
                                    |
                           +--------+--------+
                           |                 |
                        future         shared_future
                           |                 |
                       独占结果            共享结果
                       只能 move           可以 copy
                       get 一次            get 多次

从更大的并发设计视角看:

std::thread
    ↓
关注“执行流”

mutex / condition_variable / atomic
    ↓
关注“同步机制”

future / shared_future
    ↓
关注“异步结果”

promise
    ↓
关注“手动生产异步结果”

packaged_task
    ↓
关注“任务与异步结果绑定”

async
    ↓
关注“直接提交异步计算”

这也是理解这些 C++ 特性的关键:

它们并不是彼此孤立的几个 API,而是围绕同一个 shared state,在不同抽象层级上给生产者和消费者提供不同程度的控制能力。

四十、总结

这篇文章并没有从 future 的接口开始死记 API,而是先从最基本的问题出发:

单线程中存在耗时任务
        ↓
后续逻辑不依赖当前结果
        ↓
任务可以拆到其他执行流
        ↓
但结果最终还需要回来

于是最传统的方案是:

thread
+
共享资源
+
mutex
+
condition_variable

在此基础上,C++11 引入 <future> 体系,将:

异步结果
完成状态
等待同步
异常传播
生命周期

抽象为:

shared state

并围绕它形成:

生产者:
async
promise
packaged_task

消费者:
future
shared_future

其中:

future

是独占式的一次性结果句柄;

shared_future

是共享式结果句柄;

async

将任务执行和结果通道整体封装;

promise

把生产者结果写入能力单独暴露出来;

packaged_task

则把 callable 和 shared state 绑定成一个可以被线程池调度的任务对象。

最后再回到工程实践:

学习这些特性并不是为了见到新 API 就立即替换掉 thread、mutex、condition_variable,而是理解每一层抽象替我们做了什么、牺牲了什么,以及在自己的场景里到底需要多少控制权。

只有把这一层关系想清楚以后:

future
async
promise
shared_future
packaged_task

才不再是五个零散的 C++ 名词,而是一套完整的:

异步任务结果传递模型。


本次分享就到这里。技术这东西越研究越有意思,后续有新的收获我也会继续更新。

评论 (0)

暂无评论