「C++」掌握C++异步编程

在这里插入图片描述


📄前言

异步任务是多线程编程的核心,若想学习多线程设计,深入了解这些基本概念是必不可少的。如果你从未了解过这些概念,亦或者对c++异步任务的库函数有所遗忘了,不妨点进本文来学习一下。

异步任务

概念

异步任务就是在程序后台执行的任务,而异步指的是与主线程所不同步奏。用生活的例子来讲就是,我在一边看视频,一边吃饭。创建一个线程,让它执行一个函数,这就是一个简单的异步任务。你可能会觉得这也太简单了,但先不要走,其实我们要讨论的还不止如此。

C++为异步任务提供的可不止thread,还有为了异步任务返回值而诞生的 期待(future) 承诺(promise),以及打包任务所需的 packaged_task 和 用更高级的异步任务创建工具—async

惯例地讨论下优缺点:

  • 异步任务的优点:
    • 提高程序的性能:多个线程并发运行能够显著提高程序的性能(前提是有合理的设计)。
    • 提高程序响应性:一个程序可能需要等待I/O输入的同时,能够同时处理后台的各种任务(如网络数据传输)。
  • 异步任务的缺点
    • 让程序调试难度提高:多线程的 bug 可能难以重现、甚至只会在特定的机器出现问题。
    • 可能会导致死锁、竞态条件等问题:不合理的设计可能会导致程序的性能提高不显著,甚至导致程序无响应。

期待与承诺

如果你有使用过 thread 函数,那么你肯定会发现它是无法通过函数返回值来查看运算结果,虽然可以通过引用传参来获取返回值,但这样获取的数据在多线程情况下还得自己解决数据二义性问题,而 future 和 promise 提供了一种线程安全且方便的返回方式

future

future 如同其名—期待,期待一个任务结果的获取,我们不需要立刻知道结果是否就绪,只需要在我们需要用到结果时才去访问。如果此时结果还未就绪,线程就会阻塞等待这个结果的获取。

用一个生活的小例子来比喻就是:我和朋友约定去公园一起玩,我在去公园的路上不知道朋友是否已经到达,只有我到了公园才知道,我当然会期待到公园的时候他就已经到达,但如果他还没到公园,我就在此地等待。相信这个例子已经足够说明期待的本质了,在C++中 future 一般与 async 、promise、package_task 等工具一起使用

简单的函数使用演示:

#include <future>
#include <iostream>
#include <thread>
int sum(int x, int y) {
    std::cout << "线程运行ing" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));	// 模拟运行任务中。
    std::cout << "线程结束" << std::endl;
    return x + y;
}

int main() {
    std::future<int> res = std::async(sum, 1, 9);	// async的返回值是一个future。详细将在后文介绍。
    std::cout << res.get() << std::endl;	// res还未待续,线程阻塞等待res。
    
    return 0;
}

promise

promise 与 future 也是一样的“人与其名”,承诺会给 future 一个结果,而这个结果可能会及时得到,也可能会非常晚才得到。promise 需要与 future 一同使用,一般通过传参使用。

函数使用:

void func(std::promise<std::string>& ip, std::promise<std::string>& msg) {
    // 假设进行网络连接
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ip.set_value("8.8.8.8");    //获取客户端ip
    msg.set_value("Hello!");         // 获取客户端的信息.
}

int main() {
    std::promise<std::string> get_ip, get_msg;
    std::future<std::string> ip = get_ip.get_future();	// 绑定期待
    std::future<std::string> msg = get_msg.get_future();
    // 引用参数需要使用ref来保证其引用性质。
    std::thread t1(func, std::ref(get_ip), std::ref(get_msg));
    
    t1.detach();	//分离线程
    printf("[%s] %s", ip.get().c_str(), msg.get().c_str());

    return 0;
}

异常处理

future 和 promise可以用于接受异常,这也是它们的一大特点之一,如果使用 async 中发生异常,则异常会存储到它的返回值中,当调用 get() 时再次被抛出。当然使用promise也能够设置异常,然后让future 接收。

int func(int x, int y)
{
    if(y == 0)
        throw std::runtime_error("x / 0");
    else
        return x / y;
}

void errorfunc(std::promise<int>& ret)
{
    try
    {
        int res = func(29, 0);
        ret.set_value(res);
    }catch(const std::runtime_error& e)
    {   // promise 也能够储存异常
        ret.set_exception(std::current_exception() );
    }
}


int main() {
    try {
        std::promise<int> ret;
        std::future<int> f = ret.get_future();  //绑定promise
        std::thread(errorfunc, std::ref(ret)).detach();
        int result = f.get();   //异常在get()函数被抛出
        std::cout << result << std::endl;
    }catch(const std::runtime_error& e)
    {
        std::cerr << "error: " << e.what() << std::endl;
    }

    return 0;
}

执行异步任务

async

async 是 C++ 中更智能的一种创建线程的方式,它能够自动管理线程的生命周期,并且自动控制线程的 数量(程序线程过多将不会创建),它的返回值是一个带函数返回值的 future ,可以用它来得知函数的运行结果 或 函数发生的异常。

  • async 的优点:
    • 自动管理线程生命周期:线程不需要自己来管理,函数将自己管理。
    • 异常安全:future能接受函数中产生的异常

函数使用:

template <typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init)
{
    //获取数组的长度
    const unsigned long length = std::distance(first, last);

    const unsigned long min_per_thread = 25; 
    // 分块计算数据
    if(length <= 25)    //递归终点 
        return std::accumulate(first, last, init); 
    else
    {
        Iterator mid_point = first + length / 2; 
        // async会自动处理线程的数量,不用担心线程无限开销。 
        std::future<T> first_half_result = 
                std::async(parallel_accumulate<Iterator, T>, mid_point, last, init);   //处理后半部分


        // 处理前半数据	(注意这里的init要换成T())
        return first_half_result.get() + std::accumulate(first, mid_point, T());

    }
}

int main() {
    std::vector<int> nums(100);
    // 从1开始为数组填充数据1~100
    std::iota(nums.begin(), nums.end(), 1);
	
    auto sum = parallel_accumulate(nums.begin(), nums.end(), 123);
//    auto sum = std::accumulate(nums.begin(), nums.end(), 123);

    return 0;
}

// 函数递归展开图

                                          [0-100)
                                             |
                      +-----------------------------------------+
                      |                                         |
                  [0-50)                                 [50-100)
                      |                                         |
          +-------------------+                      +-------------------+
          |                   |                      |                   |
      [0-25)             [25-50)                [50-75)             [75-100)
        |                   |                      |                   |
   std::accumulate   std::accumulate        std::accumulate     std::accumulate

packaged_task

packaged_task 是用于打包异步任务的工具,它可以对普通函数、类内函数、lambda函数进行打包,然后在另一个线程中进行运行,经常用于像线程池等需要打包任务的场景。

简单的函数使用:

// 函数使用
int func(int x, int y)
{
    return x + y;
}

int main() {
    std::packaged_task<int(int, int)> task1(func);	//包装普通函数
    std::packaged_task<int()> task2([&] { return func(2, 3); });	// 包装lambda表达式

    std::future<int> res1 = task1.get_future();	// packaged_task 返回值是future
    std::future<int> res2 = task2.get_future();

    std::thread t1(std::move(task1), 9, 9).detach;	
    std::thread t2(std::move(task2) ).detach;

    std::cout << res1.get() << ":" << res2.get() << std::endl;

    return 0;
}

📓总结

在C++中异步任务常用的工具有future、promise、async、packaged_task等,掌握它们对编写一个高效的多线程至关总要,希望本文能够对你有所帮助。

工具用途
std::async用于以简化的方式启动异步任务,自动管理线程生命周期,并自动控制线程数量,返回std::future对象以获取任务的执行结果或异常。
std::future提供一种机制来访问异步操作的结果。当异步操作完成时,可以通过std::future对象获取结果或捕获在异步操作中抛出的异常。
std::promise允许在某个线程中设置值或异常,这些值或异常将在未来某个时刻通过与之关联的std::future对象被其他线程访问。
std::packaged_task封装一个可调用对象,并允许其异步执行,同时提供一个std::future对象,以便获取该可调用对象的返回值或在执行过程中捕获的异常。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 对于Visual C的网络编程源码,可以通过使用Windows套接字(Windows Socket)API来实现。Windows套接字API提供了一组函数和数据结构,用于创建和管理网络连接,以及进行网络数据传输。 首先,我们需要包含Windows套接字API的头文件Winsock2.h,并且在程序初始化时调用WSAStartup函数来初始化Windows套接字库。然后,我们需要创建一个套接字(socket),可以使用socket函数来完成。通过设置套接字的属性,如协议族(AF_INET)和套接字类型(SOCK_STREAM或SOCK_DGRAM),来确定是TCP还是UDP协议。 接下来,我们可以使用bind函数将套接字与本地IP地址和端口绑定。如果需要连接到远程主机,可以使用connect函数来建立连接。对于服务器端程序,可以使用listen函数来监听客户端连接请求,并使用accept函数接受客户端连接。 一旦建立了连接,我们可以使用send和recv函数进行数据的发送和接收。send函数将数据从缓冲区发送给对端,而recv函数从对端接收数据到缓冲区。数据的发送和接收可以是同步的或异步的,取决于应用程序的需求。 在完成网络通信后,我们可以使用closesocket函数关闭套接字,并在程序结束时调用WSACleanup函数释放套接字库的资源。 在实际应用中,可以根据具体需求进行进一步的扩展和优化,例如使用多线程处理并发连接、加入错误处理等等。 总之,通过使用Visual C编写网络编程源码,我们可以实现客户端和服务器之间的数据通信,实现网络应用程序的开发。 ### 回答2: Visual C++是一种编程语言,可以用来进行网络编程。网络编程是指通过计算机网络进行数据传输和通信的过程。下面是一个简单的Visual C++网络编程的源代码示例: ```c++ #include <iostream> #include <winsock2.h> #pragma comment(lib, "ws2_32") using namespace std; int main() { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { cout << "Failed to initialize winsock" << endl; return 1; } SOCKET listeningSocket = socket(AF_INET, SOCK_STREAM, 0); if (listeningSocket == INVALID_SOCKET) { cout << "Failed to create socket" << endl; return 1; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); serverAddr.sin_addr.s_addr = INADDR_ANY; if (bind(listeningSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { cout << "Failed to bind socket" << endl; return 1; } if (listen(listeningSocket, SOMAXCONN) != 0) { cout << "Failed to listen on socket" << endl; return 1; } cout << "Waiting for incoming connections..." << endl; sockaddr_in clientAddr; int clientAddrSize = sizeof(clientAddr); SOCKET clientSocket = accept(listeningSocket, (sockaddr*)&clientAddr, &clientAddrSize); if (clientSocket == INVALID_SOCKET) { cout << "Failed to accept client connection" << endl; return 1; } char buffer[4096]; memset(buffer, 0, sizeof(buffer)); int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0); if (bytesRead <= 0) { cout << "Failed to receive data from client" << endl; return 1; } cout << "Received data from client: " << buffer << endl; cout << "Closing the connection" << endl; closesocket(clientSocket); closesocket(listeningSocket); WSACleanup(); return 0; } ``` 以上是一个基于TCP协议的简单的服务器程序的源代码示例。它包含了Winsock库,用于进行网络编程。这个程序创建了一个用于监听连接请求的套接字(socket),并在8888端口进行监听。当接收到客户端的连接后,它会接收客户端发送的数据,并输出到控制台。然后,关闭连接并清理资源。 这只是一个简单的示例,Visual C++网络编程的功能还包括创建客户端程序、实现多线程服务器、实现UDP协议等等。网络编程是一项复杂的任务,需要充分理解计算机网络的原理和基本概念。通过学习和实践,我们可以深入了解和掌握Visual C++网络编程的技术。 ### 回答3: Visual C网络编程源码是使用Visual C编程语言编写的用于实现网络通信的代码。网络编程是指利用计算机网络进行数据交换和通信的技术,可以实现不同计算机之间的数据传输和通信。 在Visual C中进行网络编程需要使用Winsock库,该库提供了在Windows操作系统上进行网络编程所需的功能和接口。以下是一个简单的Visual C网络编程的源码示例,实现了一个基于TCP协议的简单服务器端和客户端的通信: 服务器端源码: ```cpp #include <iostream> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") // 导入ws2_32.lib库 int main() { // 初始化Winsock WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建服务器端套接字 SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 绑定IP地址和端口号 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); // 设置端口号为8888 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)); // 监听客户端请求 listen(serverSocket, 5); // 接收客户端请求 SOCKET clientSocket; sockaddr_in clientAddr; int clientAddrSize = sizeof(clientAddr); clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &clientAddrSize); // 接收和发送数据 char buffer[1024]; recv(clientSocket, buffer, sizeof(buffer), 0); // 接收客户端发送的数据 std::cout << "接收到客户端数据:" << buffer << std::endl; const char* response = "Hello, Client!"; send(clientSocket, response, strlen(response), 0); // 发送数据给客户端 // 关闭套接字 closesocket(clientSocket); closesocket(serverSocket); // 清理Winsock WSACleanup(); return 0; } ``` 客户端源码: ```cpp #include <iostream> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") // 导入ws2_32.lib库 int main() { // 初始化Winsock WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建客户端套接字 SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 设置服务器的IP地址和端口号 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); // 设置服务器端口号为8888 serverAddr.sin_addr.s_addr = inet_addr("服务器的IP地址"); // 连接服务器 connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)); // 发送和接收数据 const char* message = "Hello, Server!"; send(clientSocket, message, strlen(message), 0); // 发送数据给服务器 char buffer[1024]; recv(clientSocket, buffer, sizeof(buffer), 0); // 接收服务器返回的数据 std::cout << "接收到服务器数据:" << buffer << std::endl; // 关闭套接字 closesocket(clientSocket); // 清理Winsock WSACleanup(); return 0; } ``` 以上是一个简单的Visual C网络编程的源码示例,通过创建服务器端和客户端套接字,实现了简单的数据传输和通信。实际应用中,可以根据具体需求进行相应的修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值