C++多线程编程1_时间,线程,异步

时间:

C++引入的时间标准库:std::chrono

时间点类型:chrono::steady_clock::time_point

时间段类型:chrono::millisecondschrono::secondschrono::minutes

方便的运算符重载:时间点+时间段=时间点,时间点-时间点=时间段

auto t0 = chrono::steady_clock::now();  // 获取当前时间点
auto t1 = t0 + chrono::seconds(30);      // 当前时间点的30秒后
auto dt = t1 - t0;             // 获取两个时间点的差(时间段)
int64_t sec = chrono::duration_cast<chrono::seconds>(dt).count();  // 时间差的秒数

时间段也可以以double为返回类型:

duration_cast 可以在任意的 duration 类型之间转换

duration<T, R> 表示用 T 类型表示,且时间单位是 R

R 省略不写就是秒,std::milli 就是毫秒,std::micro 就是微秒

seconds duration<int64_t> 的类型别名

milliseconds duration<int64_t, std::milli> 的类型别名

这里我们创建了 double_ms 作为 duration<double, std::milli> 的别名

sleep_for :睡一个时间段

milliseconds 表示毫秒,也可以换成 microseconds 表示微秒,seconds 表示秒

 sleep_until:睡到时间点

 以上这二者是等价的。

进程和线程:

从属关系:进程 > 线程。一个进程可以拥有多个线程。
每个线程共享同样的内存空间,开销比较小。
每个进程拥有独立的内存空间,因此开销更大。
对于高性能并行计算,更好的是多线程。       
进程 是一个应用程序被操作系统拉起来加载到内存之后从开始执行到执行结束的这样一个过程。简单来说,进程是程序(应用程序,可执行文件)的一次执行。比如双击打开一个桌面应用软件就是开启了一个进程。
线程 是进程中的一个实体,是被系统独立分配和调度的基本单位。也有说,线程是CPU可执行调度的最小单位。也就是说,进程本身并不能获取CPU时间,只有它的线程才可以。
/*
C++20引入了一个协程的概念:
协程就是把原来每个线程分别负责的任务压缩到少量线程中,每个线程中用协程来实现原来线程级别的任务,因为包装了系统io导致协程内遇到io不会导致当前线程被挂起,以起到最大化利用时间片,减少线程调度开销的作用
*/
为什么需要多线程:无阻塞多任务
我们的程序常常需要同时处理多个任务。
例如:后台在执行一个很耗时的任务,比如下载一个文件,同时还要和用户交互。

在C++中,设立了std::thread

#include <iostream>
#include <thread>
#include <string>

void download(std::string file) {
    for (int i = 0; i < 10; i++) {
        std::cout << "Downloading " << file
                  << " (" << i * 10 << "%)..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }
    std::cout << "Download complete: " << file << std::endl;
}

void interact() {
    std::string name;
    std::cin >> name;
    std::cout << "Hi, " << name << std::endl;
}

int main() {
    std::thread t1([&] {
        download("hello.zip");
    });
    interact();
    std::cout << "Waiting for child thread..." << std::endl;
    t1.join();//设立同步点
    std::cout << "Child thread exited!" << std::endl;
    return 0;
}

 

在这里就可以看到,我输入whs是一起输入的,当他识别到了我再输入之后跟我打了个招呼,之后就告诉我要等待子线程的执行。 

而我们知道,thread是满足RAII思想和三五法则的,所以他自定义了解构函数删除了拷贝构造/赋值函数,但是提供了移动构造/赋值函数

因此,当 t1 所在的函数退出时,就会调用 std::thread 的解构函数,这会销毁 t1 线程。所以,download 函数才会出师未捷身先死——还没开始执行他的线程就被销毁了

 为了解决这种事情,可以用t1.detach()分离该对象与线程,这意味着当前现成的生命周期不由thread对象管理。

 但这样还是有问题,可以看到在上面的ppt中,虽然线程完美的退出了,但下载也退出了没有完成。

为了解决这种问题,可以创立一个全局的线程池:
 

#include <iostream>
#include <thread>
#include <string>
#include <vector>
std::vector<std::thread> pool;

void download(std::string file) {
    for (int i = 0; i < 10; i++) {
        std::cout << "Downloading " << file
            << " (" << i * 10 << "%)..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }
    std::cout << "Download complete: " << file << std::endl;
}

void interact() {
    std::string name;
    std::cin >> name;
    std::cout << "Hi, " << name << std::endl;
}

void myfunc() {
    std::thread t1([&] {
        download("hello.zip");
        });
    pool.push_back(std::move(t1));
}

int main() {
    myfunc();
    interact();
    for (auto& t : pool) t.join();
    return 0;
}

通过这种创立全局线程池的方式,我们就可以在全局操纵每个线程,就是将生存周期变得和main函数一样长。在之后将pool中的每个线程都同步就可以了。

但这样的话还是要在mian函数中修改,所以很麻烦。可以构建一个类,结构函数就是自动同步。

class ThreadPool
{
    std::vector<std::thread> pool;
public:
    void push_back(std::thread t) {
        pool.push_back(std::move(t));
    };
    ~ThreadPool() {
        for (auto& t : pool) t.join();
    };
};

ThreadPool thrp;

void myfunc() {
    std::thread t1([&] {
        download("hello.zip");
        });
    thrp.push_back(std::move(t1));
}

这样在析构的时候就自动制定了同步join()。

异步:

std::aysnc

#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <future>

int download(std::string file) {
    for (int i = 0; i < 10; i++) {
        std::cout << "Downloading " << file
            << " (" << i * 10 << "%)..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
    }
    std::cout << "Download complete: " << file << std::endl;
    return 404;
}

void interact() {
    std::string name;
    std::cin >> name;
    std::cout << "Hi, " << name << std::endl;
}

int main() {
    std::future<int> fret = std::async([&] {
        return download("hello.zip");
        });
    interact();
    int ret = fret.get();
    return 0;
}
std::async 接受一个带返回值的 lambda ,自身返回一个 std::future 对象。
lambda 的函数体将在 另一个线程 里执行。
接下来就可以在 main 里面做一些别的事情, download 会持续在后台悄悄运行。

最后调用 future get() 方法,如果此时 download 还没完成,会等待 download 完成,并获取 download 的返回值。就相当于join()。

int main() {
    std::future<int> fret = std::async([&] {
        return download("hello.zip");
        });
    interact();
    fret.wait();
    return 0;
}

除此之外,还有wait()方法。会等待线程执行完毕。但不会返回值。

但这个是只要线程没有结束,wait就会一直等待下去。

还有wait_for(),比如wait_for(std::chrono :: milliseconds(1000)),这个就是等待一千毫秒,如果一千毫秒还没完事,就不等了。

这个会返回一个std::future_status表示等待是否成功。

如果超过了时间放弃等待,会返回一个future_status::timeout

如果线程在指定时间内执行完毕,会返回一个future_status::ready

同理还有wait_until()。其参数和之前一样是一个时间点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值