现代C++多线程基础 - C++标准库中的线程

编译选项 cmake/make

std::thread 的实现背后是基于 pthread 的,为了编译通过建议:

Cmake

cmake_minimum_required(VERSION 3.10)

set(CMAKE_CXX_STANDARD 17)

project(cpptest LANGUAGES CXX)

add_executable(cpptest main.cpp)
# 重点是这一步,链接pthread库
target_link_libraries(cpptest PUBLIC pthread)

makefile

excute=out
obj=test.cpp

$(excute): $(obj)
	# 重点是 -lpthread
	gcc $(obj) -lpthread  -lstdc++ -o $(excute)

clean:
	rm $(excute)

常用操作

std::this_thread

线程控制自己的方法,在<thread>头文件中,不仅有std::thread这个类,而且还有一个std::this_thread命名空间,它可以很方便地让线程对自己进行控制。

std::this_thread是个命名空间,所以你可以使用using namespace std::this_thread;这样的语句来展开这个命名空间,不过我不建议这么做。

等待 ::sleep_for ::sleep_until

#include <thread>
// 等待到sleep_time时间点
template<class Clock, class Duration >
void sleep_until(const std::chrono::time_point<Clock, Duration>& sleep_time );
// 等待sleep_duration时间段
template<class Rep, class Period >
void sleep_for(const std::chrono::duration<Rep, Period>& sleep_duration );
#include <chrono>
#include <iostream>
#include <thread>
using std::chrono::operator""ms;
std::cout << "Hello, waiter...\n" << std::flush;
const auto start = std::chrono::steady_clock::now();

std::this_thread::sleep_until(start + 2000ms;);

std::chrono::duration<double, std::milli> elapsed {now() - start};
std::cout << "Waiteded until" << elapsed.count() << " ms\n";
using namespace std::chrono_literals;
std::cout << "Hello waiter\n" << std::flush;
const auto start = std::chrono::high_resolution_clock::now();

std::this_thread::sleep_for(2000ms);

const auto end = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double, std::milli> elapsed = end - start;
std::cout << "Waited " << elapsed << '\n';

挂起 ::yield

挂起当前线程,允许其他线程运行。

依赖于操作系统调度器的机制和系统的状态,例如:先进先出实时调度程序(Linux中的SCHED_FIFO)挂起当前线程,并将其放在准备运行的相同优先级线程的队列后面,如果没有其他具有相同优先级的线程,yield将不起作用。

#include <thread>
// 暂时放弃线程的执行,将主动权交给其他线程
void yield() noexcept;
#include <chrono>
#include <iostream>
#include <thread>
 
// "busy sleep" while suggesting that other threads run 
// for a small amount of time
void little_sleep(std::chrono::microseconds us)
{
    auto start = std::chrono::high_resolution_clock::now();
    auto end = start + us;
    do {
        // 暂时挂起
        std::this_thread::yield();
    } while (std::chrono::high_resolution_clock::now() < end);
}
 
int main() {
    auto start = std::chrono::high_resolution_clock::now();
 
    std::this_thread::sleep_for(std::chrono::microseconds(100));
 
    auto elapsed = std::chrono::high_resolution_clock::now() - start;
    std::cout << "waited for "
              << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
              << " microseconds\n";
}

获取id ::get_id

#include <thread>
// 获取当前线程id
std::thread::id get_id() noexcept;
#include <chrono>
#include <iostream>
#include <syncstream>
#include <thread>
using namespace std::chrono_literals;
 
void foo() {
    std::thread::id this_id = std::this_thread::get_id();
    std::cout << "thread " << this_id << " sleeping...\n";
    std::this_thread::sleep_for(500ms);
}
 
int main()
{
    std::jthread t1{foo};
    std::jthread t2{foo};
}

执行线程

在C中已经有一个叫做pthread的东西来进行多线程编程,但是并不好用 (如果你认为句柄、回调式编程很实用,那请当我没说),所以c++11标准库中出现了一个叫作std::thread的东西。

C++中有多种可调用对象,他们可以作为参数传给std::bind(),std::thread(), std::async(),std::call_once()等。

std::thread 立即执行

是什么

C++11 开始,为多线程提供了语言级别的支持。用 std::thread 这个类来表示线程。作为一个 C++ 类,std::thread 同样遵循 RAII 思想和三五法则。

从概念上来讲,同一个线程不允许两个 std::thread 对象管理。所以

  • 删除 拷贝 构造/赋值函数,

  • 提供 移动 构造/赋值函数。

  • 自定义了 析构函数

    std::thread对象 离开作用域(例如所在的函数退出时), std::thread 的解构函数会销毁线程。可能导致 std::thread对象的函数还没开始执行,线程就被销毁了。

返回值:可通过std::promise 或 修改共享变量(请结合std::mutexstd::atomic)。被执行函数的返回值默认被忽略。(见下一大章节)

异常:建议抛出 std::terminate

// 默认构造函数
// 创建std::thread对象,但什么也不做
thread() noexcept;
// 初始化构造函数
// 创建std::thread对象,以args为参数,开始执行fn函数(注意,开始执行)
template <class Fn, class… Args>
explicit thread(Fn&& fn, Args&&… args);
// 移动构造函数
thread(thread&& x) noexcept;
// 移动拷贝
thread& operator=(thread &&rhs);
// 禁止复制拷贝
thread( const thread& ) = delete;
// 析构函数	析构对象
~thread();

执行线程(默认)

std::thread管理的线程,何时执行与回收?

  • 执行:线程是在 std::thread象被定义时开始执行。

  • 回收:std::thread 对象析构时,必须执行过一次 join函数 或 detach函数,否则报错执行std::terminate。(哪怕函数确实完整执行完了)

    • join函数——阻塞等待线程结束并回收资源。
    • detach —— 进程执行结束时,该线程哪怕没执行完也会强制关闭。(不推荐),可能因资源未释放而导致内存泄漏。

    多次调用 join 或 detach 会abort 哦。

// 初始化构造
std::thread t1([&] { // 开始执行
    std::this_thread::sleep_for(std::chrono::microseconds(100));
});
t1.detach(); // t1.join();
if (t1.joinable()) { t1.join(); }

// 默认构造
std::thread t2;
std::cout << "before starting" << std::endl;
t2 = std::thread{foo}; // 开始执行
std::cout << "after starting" << std::endl;
t2.join();

问题:那如果我寻思,反正是默认执行,只要执行完了是不是就得了?我不阻塞等待或放弃管理行不行?

答案:不行!放弃管理也需要显示声明你放弃了管理!例如:

    {
        std::thread t1 {[] {
                std::cout << "lalal \n";
                std::this_thread::sleep_for(std::chrono::seconds(1));
                std::cout << "done \n";
            }
        };
        // t1.join();
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }
    std::cout << "exit {} \n";

明明线程已经顺利执行结束了,但是因为 std::thread 析构之前,没有执行 join 或 detach 所以存在报错,输出如下:

lalal 
done 
terminate called without an active exception
Aborted

等待、回收

前文讲过,没有执行join或detach的std::thread会在析构时会引发异常。

阻塞等待 join

waits for the thread to finish its execution

阻塞当前线程,直到 std::thread对象管理的其*this标识的线程完成执行。

换句人话:让父线程不要急着退出,等子线程也结束了再运行。

  • 子线程不是在调用join函数时才执行的
  • 主线程阻塞等待子线程结束并回收资源
// 等待线程结束并清理资源(会阻塞)
void join();
// 返回线程是否可以执行join函数
bool joinable() const noexcept;
std::thread t1([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100));
});
t1.detach();
if (t1.joinable()) {
    t1.join();
}
放弃管理 detach

permits the thread to execute independently from the thread handle

void detach();

std::thread::detach —— 分离 std::thread对象 和 将在执行的线程,线程继续独立执行。

意味着线程的生命周期不再由当前 std::thread 对象管理

  • std::thread对象的 *this 不再拥有该线程。
  • 执行过detach 而 分离的线程,像pthread执行detach后一样,在调用它的线程结束或自己结束时释放资源。
    • 线程正常结束,在函数运行完毕后自动释放,自动销毁自己。
    • 强制结束线程(不推荐),可能因资源未释放而导致内存泄漏 —— 进程退出,自动退出。
    std::thread t1([&] {
        std::this_thread::sleep_for(std::chrono::microseconds(100));
    });
    t1.detach();

返回值 std::promise

作用

一般情况下:

  • 使用std::promise获取thread创建线程的返回值。
  • 使用std::future获取async创建线程的返回值。

std::promise实际上是std::future的一个包装。如果使用thread以引用传递返回值的话,就必须要改变future的值,std::future的值不能改变,那么如何利用引用传递返回值?

答:改不了 我新建一个

  • 可以通过promfise来创建一个拥有特定值的future

    返回的是新创建的future对象,没有改变任何已有future的值

  • 在外面看起来就是,future的值不能改变,promise的值可以改变。

使用方法

如何获得thread的返回值?过程是什么样的呢?

  1. 在主线程

    • 声明一个 std::promise<T> 变量,其T类型与返回值相同。

    • 抽象出来的意义是:这个值,虽然现在还没有,但是以后会有的,会有子线程把它填上的。

  2. 在子线程中,

    • 捕获刚刚设置的 std::promise<T> 变量。(函数入参/lambda捕获)

    • std::promise<T> 设置在上一步承诺过会被填进去的值。

      即,调用 std::promiseset_value()方法。

  3. 在主线程里,

    • get_future() 获取 std::thread 的 std::future 对象,

    • 进一步 std::future::get() 可以等待并获取线程返回值。

如果线程没有执行完,这里就阻塞。对应的场景就是,哎,你不是说过以后会有返回值的吗?你不能烂尾,就阻塞在这里,弄完了返回值再走。

void download(std::string file, promise<string> &pret) {
    std::this_thread::sleep_for(std::chrono::milliseconds(400));
    pret.set_value("for promise");  // 向promise中写入值,创建future
    // return 1;
}

int main() {
	//声明一个std::promise对象pret,其保存的值类型为int  
    std::promise<string> pret;
    std::thread t1([&] {
        download("hello.zip", pret);
    });
    std::future<string> fret = pret.get_future(); // 从promise中取出future
    string ret = fret.get(); // 从future中取出返回值
    std::cout << "Download result: " << ret << std::endl;

    t1.join();
    return 0;
}
常用函数
// 默认构造函数 构造一个空的promise对象
promise();
// 带参构造函数
// 与默认构造函数相同,但使用特定的内存分配器alloc构造对象
template <class Alloc> 
promise(allocator_arg_t aa, const Alloc& alloc);
// 复制构造函数(已删除)
promise (const promise&) = delete;
// 移动构造函数
promise (promise&& x) noexcept;
// 析构函数
~promise();
set_value

std::promise<void> 的 set_value() 不接受参数,仅仅作为同步用,不传递任何实际的值。

// set_value函数
// - 设置promise的值
// - 将共享状态设为ready(将future_status设为ready)
// void特化:只将共享状态设为ready

void set_value (const T& val)
void set_value (T&& val)
// 偏特化: 引用
void promise<R&>::set_value (R& val)
// 偏特化: void
void promise::set_value (void)
get_future
// 构造一个future对象,
// 其值与promise相同,status也与promise相同
future get_future()
demo

声明保存 int 的promise

void download(std::string file, promise<int> &pret) {
    std::this_thread::sleep_for(std::chrono::milliseconds(400));
    pret.set_value(404);  // 向promise中写入值,创建future
}

int main() {
	//声明一个std::promise对象pret,其保存的值类型为int  
    std::promise<int> pret;
    std::thread t1([&] {
        auto ret = download("hello.zip", pret);
    });
    std::future<int> fret = pret.get_future(); // 从promise中取出future
    int ret = fret.get(); // 从future中取出返回值
    std::cout << "Download result: " << ret << std::endl;

    t1.join();
    return 0;
}

全局线程池

detach 的问题是进程退出时,进程不会等待所有子线程执行完毕。

另一种解法是把 t1 对象移动到一个全局变量去,再join它们,从而延长其生命周期到 myfunc 函数体外。

vector

从中取出thread,手动join

std::vector<std::thread> pool;
void myfunc() {
    std::thread t1([&] {
        std::this_thread::sleep_for(std::chrono::microseconds(100));
    });
    pool.push_back(std::move(t1)); // 移交thread的控制权(所有权)给全局变量,延长生命周期
}

void main() {
    myfunc();
    for (auto &t: pool) t.join(); // 手动join
}
线程池

在 main 里面手动 join 全部线程还是有点麻烦,

我们可以自定义一个类 ThreadPool,并用他创建一个全局变量,其解构函数会在 main 退出后自动调用。

class ThreadPool {
    std::vector<std::thread> m_pool;

public:
    void push_back(std::thread thr) {
        m_pool.push_back(std::move(thr));
    }

    ~ThreadPool() {    // 即将离开作用域(进程要结束时),自动调用
        for (auto &t: m_pool) t.join(); // 等待线程池中的线程全部结束
    }
};

ThreadPool tpool;

void myfunc() {
    std::thread t1([&] {
        std::this_thread::sleep_for(std::chrono::microseconds(100));
    });
    tpool.push_back(std::move(t1));  // 移交thread的控制权(所有权)给全局变量,延长生命周期
}

int main() {
    myfunc();
    return 0;
}

std::async 延迟执行(可选同步异步)

概念

出现原因:

  • 可以选择执行方式:

    1. 手动选择 同步执行(等同于普通的函数调用)

    2. 手动选择 异步执行

      函数的执行仍在主线程中,只是函数式编程范式意义上的异步,而不涉及到真正的多线程。

    3. 默认 交给操作系统选择

  • 返回值操作更加方便

    • std::async——std::future
    • std::thread——std::promise

不同于 std::thread,std::async是一个模板函数,所以没有成员函数,有两种构造函数:

// 异步/同步交给操作系统选择
// - 入参:带返回值的lambda及其参数
// - 返回值: std::future 对象。
template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type> 
    async (Fn&& fn, Args&&… args);
// 异步/同步手动指定选择
// - 入参:手动选择的policy、带返回值的lambda及其参数
//    policy : 
//       同步执行 launch::deferred
//       异步执行,创建新线程 launch::async
// - 返回值: std::future 对象。
template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type>
    async (launch policy, Fn&& fn, Args&&… args);

选择执行方式

默认(不指定)

视操作系统而定。

// 不显式指定,交给操作系统
std::future<int> fret = std::async([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100));
    return 1;
});
int ret = fret.get();

// 不指定,显式的交给操作系统
std::future<int> fret = std::async(
    std::launch::async | std::launch::defereed, 
    [&] {
        std::this_thread::sleep_for(std::chrono::microseconds(100));
        return 1;
	}
);

int ret = fret.get();
指定执行方式

异步或同步,根据policy参数而定。std::launch有2个枚举值和1个特殊值:

template <class Fn, class… Args>
future<typename result_of<Fn(Args…)>::type>
    async (launch policy, Fn&& fn, Args&&… args);
同步(无子线程) ::deferred

std::launch::deferred也就是 0x2

  • 同步启动

    • 创建 std::async 时,不会创建一个线程来执行,

    • 在调用future::getfuture::wait时,才启动函数体

      推迟 lambda 函数体内的运算 到 future 的 get()或 wait() 被调用时

  • 函数的执行仍在主线程中

    这只是函数式编程范式意义上的异步,而不涉及到真正的多线程。

  • 可以用这个实现惰性求值(lazy evaluation)之类。

std::future<int> fret = std::async(std::launch::deferred, [&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100)); 
    return 1;
});
// 此时才开始启动
int ret = fret.get();
异步 ::async

std::launch::async也就是 0x1

异步启动——创建async时,创建一个线程来执行,

// 此时就已经启动
std::future<int> fret = std::async(std::launch::async, [&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100)); 
    return 1;
});

int ret = fret.get();

执行线程/返回值 std::future

std::async 返回值为 std::future,通过 std::future 可以开始让线程执行。

std::future的作用,作为句柄:

  • 获取 async 返回值
  • 让 async 阻塞等待/限时等待
  • 检测 async 线程是否已结束

所以对于返回值是void的线程来说,future也同样重要。

std::async 里 lambda 的返回类型可以为 void, 这时 future 对象的类型为 std::future<void>

int main() {
	future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
	// future::get() 阻塞等待线程结束并获得返回值
	cout << val.get() << endl;
	return 0;
}
// 默认构造函数
// 	构造一个空的、无效的future对象,但可以移动分配到另一个future对象
future() noexcept;
// 复制构造函数	(已删除)
future(const future&) = delete;
// 移动构造函数	构造一个与x相同的对象并破坏x
future (future&& x) noexcept;
// 析构函数
~future();
常用方法

需要调用 future 的方法,等待线程执行完毕。

get()

调用次数:只能调用一次

行为:

  • 阻塞等待线程结束并获取返回值

    • 如果还没完成,阻塞等待,

      只要线程没有执行完,会无限等下去。

    • 获取返回值。

  • 若future类型为void,则future::get()future::wait()相同

  • 如果是同步launch::deferred的async,启动的asycn函数

T get() // 一般情况
R& future<R&>::get() // 类型为引用
void:void future::get() // 当类型为void,则与future::wait()相同
std::future<int> fret = std::async([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100)); 
    return 1;
});
// 等待线程执行结束
int ret = fret.get();
wait()

调用次数:只能调用一次

行为:

  • 阻塞等待线程结束

    如果还没完成,阻塞等待,只要线程没有执行完,会无限等下去。

  • 不会获取返回值。

    但是可以在future::wait()结束后,再次通过 future::get()获取返回值

  • 如果是同步launch::deferred的async,启动的asycn函数

std::future<int> fret = std::async([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100));
    return 1;
});
// 等待线程执行结束,不获取返回值
fret.wait();
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
wait_for() wait_until()
wait_for()

调用次数:无限制

行为:

  • 如果还没完成,阻塞等待,直到线程结束/限定时间到

    • 可以用 chrono 里的类表示单位,指定一个最长等待时间。
    • 线程结束/限定时间到 结束阻塞
  • 返回值类型: std::future_status 表示等待是否成功。

    • 返回 std::future_status::timeout

      线程在指定的时间内没有执行完毕,放弃等待

    • 返回 std::future_status::ready

      线程在指定的时间内执行完毕,等待成功

  • 如果是同步launch::deferred的async,启动的asycn函数

template <class Rep, class Period>
  future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const;
std::future<int> fret = std::async([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100)); 
    return 1;
});

while (true) {
    // 循环多次等待
    auto stat = fret.wait_for(std::chrono::milliseconds(1000));
    if (stat == std::future_status::ready) {
        std::cout << "Future is ready!!" << std::endl;
        break;
    } else {
        std::cout << "Future not ready!!" << std::endl;
    }
}
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
wait_until()

同理 wait_for() ,其参数是一个时间点。

std::future<int> fret = std::async([&] {
    std::this_thread::sleep_for(std::chrono::microseconds(100));
    return 1;
});

while (true) {
    // 循环多次等待
    auto stat = fret.wait_for(std::chrono::milliseconds(1000));
    if (stat == std::future_status::ready) {
        std::cout << "Future is ready!!" << std::endl;
        break;
    } else {
        std::cout << "Future not ready!!" << std::endl;
    }
}
// 虽然已经结束,但还是可以获取返回值
int ret = fret.get();
std::shared_future

std::future 为了满足三五法则,删除了拷贝构造/赋值函数。如果需要浅拷贝,实现共享同一个 future 对象,可以用 std::shared_future。

void download(std::string file) {
    std::this_thread::sleep_for(std::chrono::milliseconds(400));
    std::cout << "Download complete: " << file << std::endl;
}

int main() {
    std::shared_future<void> fret = std::async([&] {
        std::this_thread::sleep_for(std::chrono::microseconds(100)); 
    });
    auto fret2 = fret;
    auto fret3 = fret;

    fret3.wait();
    std::cout << "Download completed" << std::endl;
    return 0;
}

示例

#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>
 
std::mutex m;
 
struct X
{
    void foo(int i, const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << ' ' << i << '\n';
    }
 
    void bar(const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }
 
    int operator()(int i)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        return i + 10;
    }
};
 
template<typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
    auto len = end - beg;
    if (len < 1000)
        return std::accumulate(beg, end, 0);
 
    RandomIt mid = beg + len / 2;
    auto handle = std::async(std::launch::async,
                             parallel_sum<RandomIt>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}
 
int main()
{
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
 
    X x;
    // Calls (&x)->foo(42, "Hello") with default policy:
    // may print "Hello 42" concurrently or defer execution
    auto a1 = std::async(&X::foo, &x, 42, "Hello");
    // Calls x.bar("world!") with deferred policy
    // prints "world!" when a2.get() or a2.wait() is called
    auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
    // Calls X()(43); with async policy
    // prints "43" concurrently
    auto a3 = std::async(std::launch::async, X(), 43);
    a2.wait();                     // prints "world!"
    std::cout << a3.get() << '\n'; // prints "53"
} // if a1 is not done at this point, destructor of a1 prints "Hello 42" here
The sum is 10000
43
world!
53
Hello 42

std::packaged_task 延迟异步执行

std::packaged_task - cppreference.com

template< class R, class ...ArgTypes >
class packaged_task<R(ArgTypes...)>;

std::packaged_task<F>,绑定任何可调用对象(函数、lambda表达式、绑定表达式或其他函数对象),可获取future句柄,以便可以在未来进行异步调用

封装任何Callable目标,它的返回值或抛出的异常存储在共享状态中,可以通过std::future对象访问。

  • 更方便的,天然异步的函数对象(async除非显式声明,否则不一定异步执行)。

  • 返回值:需要将可调对象传递给关联的std::future对象(通过get_future成员函数)

    • 包装std::promise<T= std::function<F>>中的可调对象T= std::function<F>

    • 可调用对象的执行、返回值获取,需要透过 future

int download02() {
    cout << "start download " << endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(400));
    cout << "set promise value " << endl;
    return 2;
}

int main()
{
    // 给packaged_task绑定函数对象
    std::packaged_task<int()> t(download02);
    // 指定函数对象的future返回值 
    std::future<int> fu2 = t.get_future();
    // 执行packaged_task绑定的函数对象
    t();
    // 先执行之后,才可以获取返回值
    int result = fu2.get();
    std::cout << result << std::endl;
    return 0;
}
std::packaged_task<int(int, int)> task([](int a, int b) {
    return std::pow(a, b); 
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "task_lambda:\t" << result.get() << '\n';
int f(int x, int y) { return std::pow(x, y); }

std::packaged_task<int()> task(std::bind(f, 2, 11));
std::future<int> result = task.get_future();
task();
std::cout << "task_bind:\t" << result.get() << '\n';

考虑这样一个场景:多个线程共享一个任务队列

  • 一个线程负责生产任务
    • 将任务放到任务队列中
    • 在这个任务执行后获取它的返回值.
  • 多个子线程从任务队列中取出任务并执行

这里简化一下这个场景。主线程产生任务,一个子线程t1执行。

std::invoke_result_t

std::packaged_task

std::jthread 自动join

C++20 引入了 std::jthread 类,

和 std::thread 不同在于:

  • 他的解构函数里会自动调用 join() 函数,
  • 保证了解构时会自动等待线程执行完毕。

native_handler

jthread 自动join

std::call_once 执行一次

在多线程的环境下, 某些可执行对象只需要执行一次。std::call_once 应运而生。

  • 封装了加锁和修改flag的过程。简化代码(不再需要判空/添加flag),增强可读性。
  • 本身不具有类似 thread.join() 的功能。需要子线程join 或 主线程进行等待

原理:判断全局变量标识符once_flag。如果这个once_flag

  • “未执行过”
    • 加锁,执行,结束后释放锁mutex
    • 修改标识符为**“执行过”**
  • “执行过” 被标记过。不再执行。
template< class Function, class... Args >
void call_once (std::once_flag& flag, Function&& f, Args&& args... );
// 参数解析:
// std::once_flag flag  判断是个需要执行。若执行,执行后关闭。
// f 和 args... 需要被调用的函数以及函数f的传参
//  lambda 常用 [this]
// 抛出异常 std::system_error if any condition prevents calls to call_once from executing as specified any exception thrown by f
// 初始化资源(节省数组判空)
// 单例模式
#include <functional>
#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag1;

class PySingleton {
public:
static PySingleton* mInstance;
static PySingleton* get_instance() {
    std::call_once(flag1, [] (int id) {
        mInstance = new PySingleton(id);
    }, 1);
    return mInstance;
}

private:
PySingleton(int id) {
    std::cout << " do init, id = " << id << " \n";
}
};

PySingleton* PySingleton::mInstance = nullptr; // 必须在这里 为static mInstance,编译期执行  


void test() {
    PySingleton* py = PySingleton::get_instance();
}

int main()
{
    std::thread mThread(test);
    mThread.join();
    return 0;
}
#include <functional>
#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag flag1;

void do_print1() {
    std::cout << "do_print1 example: called once\n"; 
}
void do_print2() {
    std::cout << "do_print2 example: called once\n"; 
}

void simple_do_once()
{
    std::function<void()> mFunc1 = do_print1;
    std::function<void()> mFunc2 = do_print2;
    std::call_once(flag1, mFunc1); // 仅仅执行了一次这个
    std::call_once(flag1, mFunc2);
}
 
int main()
{
    std::thread st1(simple_do_once);
    std::thread st2(simple_do_once); // 什么都没做
    st1.join();
    st2.join();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值