JsonCpp库、Muduo库、C++11异步操作
1. JsonCpp库
1.1 Json数据格式
Json 是一种数据交换格式,它采用完全独立于编程语言的文本格式来存储和表示数据。
例如: 我们想表示一个同学的学生信息
C 代码表示
char *name = "xx";
int age = 18;
float score[3] = {88.5, 99, 58};
Json 表示
{
"姓名" : "xx",
"年龄" : 18,
"成绩" : [88.5, 99, 58],
"爱好" :{
"书籍" : "西游记",
"运动" : "打篮球"
}
}
Json 的数据类型包括对象,数组,字符串,数字等。
- 对象:使用花括号 {} 括起来的表示一个对象
- 数组:使用中括号 [] 括起来的表示一个数组
- 字符串:使用常规双引号 “” 括起来的表示一个字符串
- 数字:包括整形和浮点型,直接使用
1.2 JsonCpp介绍
Jsoncpp 库主要是用于实现 Json 格式数据的序列化和反序列化,它实现了将多个数据对象组织成为 json 格式字符串,以及将 Json 格式字符串解析得到多个数据对象的功能。
Json::Value类:中间数据存储类
如果要将数据对象进行序列化,就需要先存储到Json:Value对象中
如果要将数据传进行反序列化,就是解析后,将数据对象放入到Json:Value对象中
class Json::Value{
Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value& operator[](const std::string& key);//简单的⽅式完成val["name"] = "xx";
Value& operator[](const char* key);
Value removeMember(const char* key);//移除元素
const Value& operator[](ArrayIndex index) const; //val["score"][0]
Value& append(const Value& value);//添加数组元素val["score"].append(88);
ArrayIndex size() const;//获取数组元素个数 val["score"].size();
std::string asString() const;//转string string name = val["name"].asString();
const char* asCString() const;//转char* char *name = val["name"].asCString();
Int asInt() const;//转int int age = val["age"].asInt();
float asFloat() const;//转float float weight = val["weight"].asFloat();
bool asBool() const;//转 bool bool ok = val["ok"].asBool();
};
Jsoncpp 库主要借助三个类以及其对应的少量成员函数完成序列化及反序列化
- 序列化接口
Json:StreamWriter类:用于进行数据序列化
Json:StreamWriter::write序列化函数
Json:StreamWriterBuilder类:Json:StreamWriter工厂类-用于生产Json:StreamWriter对象
class JSON_API StreamWriter {
virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
virtual StreamWriter* newStreamWriter() const;
}
- 反序列化接口
Json:CharReader类:反序列化类
Json:CharReader.:parse反序列化函数
Json:CharReaderBuilder:Json:CharReader工厂类-用于生产Json:CharReader对象
class JSON_API CharReader {
virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
virtual CharReader* newCharReader() const;
}
1.3 JsonCpp使用
序列化
#include<iostream>
#include<jsoncpp/json/json.h>
#include<string>
#include<memory>
#include<sstream>
//风格一序列化
bool serializeversion1(const Json::Value& root,std::string& body)
{
//1. 先实例化一个工厂类对象
Json::StreamWriterBuilder swb;
// 解决中文转化为Unicode格式
swb["emitUTF8"] = true;
//2. 通过工厂类对象生产派生类对象,并用基类对象指针指向
//通过智能指针管理,不然忘记释放就会造成资源泄漏
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
//需要一个输出流
std::stringstream ss;
int ret = sw->write(root,&ss);
if (ret != 0) {
std::cout << "json serialize failed!\n";
return false;
}
body = ss.str();
return true;
}
//风格二序列化
void serializeversion2(const Json::Value& root,std::string& body)
{
Json::FastWriter fw;
body = fw.write(root);
Json::StyledWriter sw;
body = sw.write(root);
}
int main()
{
const char *name = "张三";
int age = 23;
const char *sex = "男";
float score[3] = {88, 77.5, 66};
Json::Value student;
student["姓名"] = name;
student["年龄"] = age;
student["性别"] = sex;
//添加数组元素
student["成绩"].append(score[0]);
student["成绩"].append(score[1]);
student["成绩"].append(score[2]);
Json::Value fav;
fav["书籍"] = "西游记";
fav["运动"] = "打篮球";
student["爱好"] = fav;
std::string body;
serializeversion1(student,body);
serializeversion2(student,body);
std::cout << body << std::endl;
return 0;
}
反序列化
bool unserialize(const std::string& body,Json::Value& root)
{
//1. 先实例化一个工厂类对象
Json::CharReaderBuilder crb;
//2. 通过工厂类对象生产派生类对象,并用基类对象指针指向
//通过智能指针管理,不然忘记释放就会造成资源泄漏
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string errs;
bool ret = cr->parse(body.c_str(),body.c_str() + body.size(),&root,&errs);
if(ret == false)
{
std::cout << "json unserialize failed!\n";
return false;
}
return true;
}
bool unserialize2(const std::string& body,Json::Value& root)
{
Json::Reader reader;
bool ret = reader.parse(body,root);
if(ret == false)
{
std::cout << "json unserialize failed!\n";
return false;
}
return true;
}
int main()
{
const char *name = "张三";
int age = 23;
const char *sex = "男";
float score[3] = {88, 77.5, 66};
Json::Value student;
student["姓名"] = name;
student["年龄"] = age;
student["性别"] = sex;
//添加数组元素
student["成绩"].append(score[0]);
student["成绩"].append(score[1]);
student["成绩"].append(score[2]);
Json::Value fav;
fav["书籍"] = "西游记";
fav["运动"] = "打篮球";
student["爱好"] = fav;
std::string body;
serializeversion1(student,body);
//serializeversion2(student,body);
//std::cout << body << std::endl;
Json::Value val;
//bool ret = unserialize(body,val);
bool ret = unserialize2(body,val);
if(ret == false)
{
return -1;
}
std::cout<<val["姓名"].asString()<<std::endl;
std::cout<<val["年龄"].asUInt()<<std::endl;
std::cout<<val["性别"].asString()<<std::endl;
int sz = val["成绩"].size();
for(int i = 0; i < sz; ++i)
{
std::cout<<val["成绩"][i].asFloat()<<std::endl;
}
std::cout<<val["爱好"]["书籍"].asString()<<std::endl;
std::cout<<val["爱好"]["运动"].asString()<<std::endl;
return 0;
}
2. Muduo库
Muduo由陈硕大佬开发,是一个基于非阻塞IO和事件驱动的C++高并发TCP网络编程库。 它是一款基于主从Reactor模型的网络库,其使用的线程模型是one loop per thread,所谓one loop per thread指的是:
一个线程只能有一个事件循环(EventLoop), ⽤于响应计时器和IO事件
一个文件描述符只能由一个线程进行读写,换句话说就是一个TCP连接必须归属于某个EventLoop管理
2.1 Muduo库常见接口介绍
2.1.1 TcpServer类基础介绍
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
typedef std::function<void(const TcpConnectionPtr &)> ConnectionCallback;
typedef std::function<void(const TcpConnectionPtr &, Buffer *,Timestamp)>MessageCallback;
class InetAddress : public muduo::copyable
{
public:
//将ip和port构造成一个InetAddress对象
InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);
};
class TcpServer : noncopyable
{
public:
//是否启动地址复用
enum Option
{
kNoReusePort,
kReusePort,
};
TcpServer(EventLoop *loop,
const InetAddress &listenAddr,
const string &nameArg,
Option option = kNoReusePort);
void setThreadNum(int numThreads);//设置子Reactor数量
void start();//启动服务器,设置对监听套接字读事件关系
/// 当⼀个新连接建⽴成功/关闭连接的回调函数
void setConnectionCallback(const ConnectionCallback &cb)
{
connectionCallback_ = cb;
}
/// 消息的业务处理回调函数---这是收到新连接消息的时候回调函数
void setMessageCallback(const MessageCallback &cb)
{
messageCallback_ = cb;
}
};
这里启动服务器只是设置对监听套接字读事件关系,并没有启动事件监控,实际上是把socket和事件监控分开了。EventLoop负责对IO事件进行监控。
2.1.2 EventLoop类基础介绍
EventLoop负责IO事件监控
class EventLoop : noncopyable
{
public:
/// Loops forever.
/// Must be called in the same thread as creation of the object.
void loop();//启动事件监控
/// Quits loop.
/// This is not 100% thread safe, if you call through a raw pointer,
/// better to call through shared_ptr<EventLoop> for 100% safety.
void quit();//退出事件监控
//以下都是定时任务的处理
TimerId runAt(Timestamp time, TimerCallback cb);//指定时间运行定时任务
/// Runs callback after @c delay seconds.
/// Safe to call from other threads.
TimerId runAfter(double delay, TimerCallback cb);//延迟多少秒后运行定时任务
/// Runs callback every @c interval seconds.
/// Safe to call from other threads.
TimerId runEvery(double interval, TimerCallback cb);//每隔几秒就运行一下定时任务
/// Cancels the timer.
/// Safe to call from other threads.
void cancel(TimerId timerId);//取消定时任务
private:
std::atomic<bool> quit_;
std::unique_ptr<Poller> poller_;
mutable MutexLock mutex_;
std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);
};
通过TcpServer和EventLoop就可以搭建一个服务器
2.1.3 TcpConnection类基础介绍
针对连接的通信做管理
class TcpConnection : noncopyable, public std::enable_shared_from_this<TcpConnection>
{
public:
/// Constructs a TcpConnection with a connected sockfd
///
/// User should not create this object.
TcpConnection(EventLoop *loop,
const string &name,
int sockfd,
const InetAddress &localAddr,
const InetAddress &peerAddr);
//判断当前连接处于什么状态
bool connected() const { return state_ == kConnected; }
bool disconnected() const { return state_ == kDisconnected; }
//发送数据,实际是把数据放到发送缓存区,打开写事件监控
void send(string &&message); // C++11
void send(const void *message, int len);
void send(const StringPiece &message);
// void send(Buffer&& message); // C++11
void send(Buffer *message); // this one will swap data
//关闭连接,实际还要去看发送缓存区是否还有数据,等把发送缓存区数据发送完了
//或者出错了,才真正关闭连接
void shutdown(); // NOT thread safe, no simultaneous calling
//设置上下文
void setContext(const boost::any &context)
{
context_ = context;
}
//获取上下文
const boost::any &getContext() const
{
return context_;
}
boost::any *getMutableContext()
{
return &context_;
}
//在TcpServer就已经给每个连接的TcpConnection对象设置过了
void setConnectionCallback(const ConnectionCallback &cb)
{
connectionCallback_ = cb;
}
void setMessageCallback(const MessageCallback &cb)
{
messageCallback_ = cb;
}
private:
enum StateE
{
kDisconnected,
kConnecting,
kConnected,
kDisconnecting
};
EventLoop *loop_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
boost::any context_;
};
2.1.4 Buffer类基础介绍
class Buffer : public muduo::copyable
{
public:
static const size_t kCheapPrepend = 8;
static const size_t kInitialSize = 1024;
explicit Buffer(size_t initialSize = kInitialSize)
: buffer_(kCheapPrepend + initialSize),
readerIndex_(kCheapPrepend),
writerIndex_(kCheapPrepend);
void swap(Buffer &rhs)
//获取缓存区可读空间大小
size_t readableBytes() const
//获取缓存区可写空间大小
size_t writableBytes() const
//获取缓存区中数据的起始地址
const char *peek() const
const char *findEOL() const
const char *findEOL(const char *start) const
void retrieve(size_t len)
void retrieveInt64()
void retrieveInt32()
void retrieveInt16()
void retrieveInt8()
//从缓存区取出所有数据,当作string返回,并删除缓存区中的数据
string retrieveAllAsString()
//从缓存区取出len长度的数据,当作string返回,并删除缓存区中的数据
string retrieveAsString(size_t len)
void append(const StringPiece &str)
void append(const char * /*restrict*/ data, size_t len)
void append(const void * /*restrict*/ data, size_t len)
char *beginWrite()
const char *beginWrite() const
void hasWritten(size_t len)
void appendInt64(int64_t x)
void appendInt32(int32_t x)
void appendInt16(int16_t x)
void appendInt8(int8_t x)
int64_t readInt64()
//数据读取位置向后偏移4字节,本质上就是删除起始位置的4字节数据
int32_t readInt32()
int16_t readInt16()
int8_t readInt8()
int64_t peekInt64() const
//尝试从缓存区获取4字节的数据,进行网络字节序转换成整型,但是数据并不从缓存区删除
//查看正文长度大小,如果缓存区数据足够正文大小,就把前4字节以及后面正文都从缓存区取走
//如果不够就不取走,前4个字节也正好没有取走
int32_t peekInt32() const
int16_t peekInt16() const
int8_t peekInt8() const
void prependInt64(int64_t x)
void prependInt32(int32_t x)
void prependInt16(int16_t x)
void prependInt8(int8_t x)
void prepend(const void * /*restrict*/ data, size_t len)
private:
std::vector<char> buffer_;//vector进行缓存区内存管理
size_t readerIndex_;
size_t writerIndex_;
static const char kCRLF[];
};
2.1.5 TcpClient类基础介绍
TcpClient只是创建一个套接字,它也是通过一个EventLoop进行IO事件监控以及处理的,因此也有设置连接建立成功/连接关闭的回调函数,收到消息的回调函数。
Muduo库的IO事件都是通过EvevtLoop进行事件监控的,一旦对应连接的事件就绪了,就会调用对应连接的Connection对象里面曾经设置的读写等事件就绪的回调。假如是读事件就绪,就会先把数据读到Connection里面的Buffer缓存区,然后在调用在TcpClient设置过的新数据来了的回调函数进行处理。所以我们需要设置回到函数进行处理。并没有直接发送消息接收消息的接口。
class TcpClient : noncopyable
{
public:
// TcpClient(EventLoop* loop);
// TcpClient(EventLoop* loop, const string& host, uint16_t port);
TcpClient(EventLoop *loop,
const InetAddress &serverAddr,
const string &nameArg);
~TcpClient(); // force out-line dtor, for std::unique_ptr members.
void connect(); // 连接服务器
void disconnect(); // 关闭连接
void stop();
//获取客户端对应的通信连接Connection对象的接口,有了Connection就可以去发送数据了
//TcpClient并没有直接提供发送接口,而是先获取客户端对应的Connection对象,在去发送数据
TcpConnectionPtr connection() const
{
MutexLockGuard lock(mutex_);
return connection_;
}
// 连接服务器成功时的回调函数
void setConnectionCallback(ConnectionCallback cb)
{
connectionCallback_ = std::move(cb);
}
// 收到服务器发送的消息时的回调函数
void setMessageCallback(MessageCallback cb)
{
messageCallback_ = std::move(cb);
}
private:
EventLoop *loop_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
TcpConnectionPtr connection_ GUARDED_BY(mutex_);
};
/*
需要注意的是,因为muduo库不管是服务端还是客户端都是异步操作,
TcpClient的connect是一个非阻塞接口,
对于客户端来说如果我们在连接还没有完全建立成功的时候就有可能获取客户端
对应的Connection对象,然后调用内部send发送数据,这是不被允许的!
因此我们可以使⽤内置的CountDownLatch类进⾏同步操作控制
*/
class CountDownLatch : noncopyable
{
public:
explicit CountDownLatch(int count);
//计数大于0则阻塞
void wait()
{
MutexLockGuard lock(mutex_);
while (count_ > 0)
{
condition_.wait();
}
}
//计数--,为0时唤醒wait
void countDown()
{
MutexLockGuard lock(mutex_);
--count_;
if (count_ == 0)
{
condition_.notifyAll();
}
}
int getCount() const;
private:
mutable MutexLock mutex_;
Condition condition_ GUARDED_BY(mutex_);
int count_ GUARDED_BY(mutex_);
};
2.2 Muduo库实现字典服务端
#include<iostream>
#include<string>
#include<unordered_map>
#include<stdint.h>
//服务器所需要的muduo库中的类
#include<muduo/net/TcpServer.h>
#include<muduo/net/EventLoop.h>
#include<muduo/net/TcpConnection.h>
#include<muduo/net/Buffer.h>
class DictServer
{
public:
DictServer(uint16_t port)
:_server(&_baseloop,muduo::net::InetAddress("0.0.0.0",port),
"DictServer",muduo::net::TcpServer::kReusePort)
{
//设置连接事件(连接建立/管理)的回调
_server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));
//设置连接消息的回调
_server.setMessageCallback(std::bind(&DictServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
void start()
{
_server.start();//先开始监听
_baseloop.loop();//开始死循环事件监控
}
private:
//连接建立/连接关闭回调函数
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if(conn->connected())
{
std::cout<<"连接建立"<<std::endl;
}
else
{
std::cout<<"连接断开"<<std::endl;
}
}
//业务处理回调函数
void onMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf, muduo::Timestamp)
{
static std::unordered_map<std::string, std::string> dict_map = {
{"hello", "你好"},
{"world", "世界"},
{"bite", "比特"}
};
std::string msg = buf->retrieveAllAsString();
std::string res;
auto it = dict_map.find(msg);
if (it != dict_map.end()) {
res = it->second;
}else {
res = "未知单词!";
}
conn->send(res);
}
private:
muduo::net::EventLoop _baseloop;
muduo::net::TcpServer _server;
};
int main()
{
DictServer server(8080);
server.start();
return 0;
}
设置回调函数、设置线程数量、对监听套接字读事件关心,启动事件监控
IFLAG= -I ../build/release-install-cpp11/include/
LFLAG= -L ../build/release-install-cpp11/lib/
server:server.cc
g++ $(IFLAG) $(LFLAG) -o $@ $^ -std=c++11 -lmuduo_net -lmuduo_base -lpthread
2.3 Muduo库实现字典客户端
#include<iostream>
#include<string>
#include<stdint.h>
#include<muduo/net/TcpClient.h>
#include<muduo/net/EventLoop.h>
#include<muduo/net/TcpConnection.h>
#include<muduo/net/Buffer.h>
#include<muduo/base/CountDownLatch.h>
#include<muduo/net/EventLoopThread.h>
class DictClient
{
public:
DictClient(const std::string& ip,uint16_t port)
:_baseloop(_loopthread.startLoop()),
_downlatch(1),
_client(_baseloop,muduo::net::InetAddress(ip,port),"DictClient")
{
_client.setConnectionCallback(std::bind(&DictClient::onConnection,this,std::placeholders::_1));
_client.setMessageCallback(std::bind(&DictClient::onMessage,this,std::placeholders::_1,
std::placeholders::_2,std::placeholders::_3));
}
void connect()
{
_client.connect();
_downlatch.wait();
//什么时候启动IO事件监控?
//事件监控内部可是一个死循环,在构造启动或者其他地方启动那就一直死循环下去
//因此创建一个EventLoop对应的线程专门负责对事件进行监控
}
void shutdown()
{
_client.disconnect();
}
bool send(const std::string msg)
{
if (_conn->connected() == false) {
std::cout << "连接已经断开,发送数据失败!\n";
return false;
}
_conn->send(msg);
return true;
}
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if(conn->connected())
{
std::cout<<"连接建立"<<std::endl;
_downlatch.countDown();
_conn = conn;
}
else
{
std::cout<<"连接断开"<<std::endl;
_conn.reset();
}
}
void onMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buf, muduo::Timestamp)
{
std::string str = buf->retrieveAllAsString();
std::cout<<str<<std::endl;
}
private:
//创建一个EventLoop对应的线程进行IO事件监控
muduo::net::EventLoopThread _loopthread;
//connect是一个非阻塞接口,防止连接还没有建立成功,就通过TcpConnection的send接口发送消息
muduo::CountDownLatch _downlatch;
//获取客户端对应的TcpConnection
muduo::net::TcpConnectionPtr _conn;
muduo::net::EventLoop* _baseloop;
muduo::net::TcpClient _client;
};
int main()
{
DictClient client("127.0.0.1",8080);
client.connect();
while(1) {
std::string msg;
std::cin >> msg;
client.send(msg);
}
client.shutdown();
return 0;
}
设置回调函数、发起连接
3. C++11 异步操作
3.1 std::future
std::future是C++11标准库中的一个模板类,它表示一个异步操作的结果。当我们在多线程编程中使用异步任务时,std::future可以帮助我们在需要的时候获取任务的执行结果。std::future的一个重要特性是能够阻塞当前线程,直到异步操作完成,从而确保我们在获取结果时不会遇到未完成的操作。
std::future用来表示一个异步任务的结果,或者说用于同步保存一个异步任务的结果。
同步和异步的区别:一个任务是否是当前执行流/进程自身完成的。
同步是当前执行流/进程自身完成的,异步是其他执行流/线程完成的。
应用场景
- 异步任务:当我们需要在后台执行一些耗时操作时,如网络请求或计算密集型任务等,std::future可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率
- 并发控制: 在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作
- 结果获取:std::future提供了一种安全的方式来获取异步任务的结果。我们可以使用std::future::get()函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调用get()函数时,我们可以确保已经获取到了所需的结果
3.2 用法
std:future并不能单独使用,需要搭配一些能够执行异步任务的模板类或函数一起使用。
3.2.1 使用std::async和std::future配合
std::async是一种将任务与std::future关联的简单方法。它创建并运行一个异步任务,并返回一个与该任务结果关联的std::future对象用于获取函数结果。默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的参数。这个参数为std::launch类型:
- std::launch::deferred 表明该函数会被延迟调用,直到在future上调用get()或者wait()才会开始执行任务
- std::launch::async 表明会创建一个线程去运行函数
- std::launch::deferred | std::launch::async 内部通过系统等条件自动选择策略
#include<iostream>
#include<future>
int Add(int x, int y)
{
return x + y;
}
int main()
{
//std::launch::async策略:内部创建一个线程执行函数,函数运行结果通过future获取
//std::launch::deferred策略:同步策略,获取结果的时候再去执行任务
std::future<int> ret = std::async(std::launch::async,Add,11,22);
std::future<int> ret = std::async(std::launch::deferred,Add,11,22);
//std::future<int>::get() 用于获取异步任务的结果,如果还没有结果就会阻塞
std::cout<<ret.get()<<std::endl;
return 0;
}
3.2.2 使用std::packaged_task和std::future配合
std::packaged_task就是将任务和std::feature绑定在一起的类模板,是一种对任务的封装。我们可以通过std::packaged_task对象调用get_future()方法获取任务相关联的std::feature对象。std::packaged_task的模板参数是函数签名。
std::packaged_task类模板:为一个函数生成一个异步任务对象(可调用对象),用于在其他线程中执行。
可以把std::future和std::async看成是分开的, 而 std::packaged_task则是一个整体
#include<iostream>
#include<future>
#include<thread>
#include<memory>
int Add(int x, int y)
{
return x + y;
}
int main()
{
// 1.封装任务
// std::packaged_task<int(int, int)> task(add);
// 此处可执⾏其他操作, ⽆需等待
// std::cout << "hello bit!" << std::endl;
// 2.获取任务包关联的future对象
// std::future<int> result_future = task.get_future();
// 3.执行任务
// std::thread t([&]{
// task(11,22);
// });
// 4.获取结果
// std::cout<<res.get()<<std::endl;
// t.join();
// 需要注意的是,task虽然重载了()运算符,但task并不是⼀个函数,
// std::async(std::launch::async, task, 1, 2); //--错误用法
// 所以导致它作为线程的入口函数时,语法上看没有问题,但是实际编译的时候会报错
// std::thread(task, 1, 2); //---错误用法
// ⽽packaged_task禁⽌了拷贝构造
// 且因为每个packaged_task所封装的函数签名都有可能不同,因此也⽆法当作参数⼀样传递
// 传引用不可取,毕竟任务在多线程下执⾏存在局部变量声明周期的问题,因此不能传引用
// 因此想要将⼀个packaged_task进⾏异步调用
// 简单⽅法就只能是new packaged_task,封装函数传地址进⾏解引用调用了
// ⽽类型不同的问题,在使用的时候可以使用类型推导来解决
// 1.封装任务
auto task = std::make_shared<std::packaged_task<int(int,int)>>(Add);
// 2.获取任务包关联的future对象
std::future<int> res = task->get_future();
// 3.执行任务
std::thread t([&]{
(*task)(11,22);
});
// 4.获取结果
std::cout<<res.get()<<std::endl;
t.join();
return 0;
}
3.2.3 使用std::promise和std::future配合
std::promise提供了一种设置值的方式,它可以在设置之后通过相关联的std::future对象进行读取。换种说法就是之前说过std::future可以读取一个异步函数的返回值了, 但是要等待就绪, 而std::promise就提供一种方式手动让 std::future就绪。
std:promise类模板:实例化的对象可以返回一个future,在其他线程中向promise对象设置数据,其他线程的关联future就可以获取数据。
#include<iostream>
#include<future>
#include<thread>
int Add(int x,int y)
{
return x + y;
}
int main()
{
//1.在使用的时候,就是先实例化一个指定结果的promise对象
std::promise<int> pro;
//2.通过promise对象,获取关联的future对象
std::future<int> res = pro.get_future();
//3.在任意位置给promise设置数据,就可以通过关联的future获取到这个设置的数据了
std::thread thr([&]{
int sum = Add(11,22);
pro.set_value(sum);
});
std::cout<<res.get()<<std::endl;
thr.join();
return 0;
}