主要新特性有类的初始化与函数、新增关键字、std库新特性、lambda表达式、智能指针、线程等
由于内容太多拆分分几个章节讲解
c++11/14新特性—关键字
c++11/14新特性—函数语法
c++11/14新特性—智能指针
c++11/14新特性—线程

多线程

构造函数

// 不执行任何处理动作
thread() noexcept;
// 将 other 的线程所有权转移给新的 thread 对象 之后 other 不再表示执行线程
thread( thread&& other ) noexcept;
// 创建线程对象,并在该线程中执行函数 f 中的业务逻辑,args 是要传递给函数 f 的参数
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
// 不允许线程对象之间的拷贝
thread( const thread& ) = delete;

获取线程id

std::thread::id get_id() const noexcept;

回收线程

void join();

分离线程

void detach();

判断主线程和子线程是否处理关联(连接)状态:

  1. 在创建的子线程对象的时候,如果没有指定任务函数,那么子线程不会启动,主线程和这个子线程也不会进行连接
  2. 在创建的子线程对象的时候,如果指定了任务函数,子线程启动并执行任务,主线程和这个子线程自动连接成功
  3. 子线程调用了detach()函数之后,父子线程分离,同时二者的连接断开,调用joinable()返回false
  4. 在子线程调用了join()函数,子线程中的任务函数继续执行,直到任务处理完毕,这时join()会清理(回收)当前子线程的相关资源,所以这个子线程和主线程的连接也就断开了,因此,调用join()之后再调用joinable()会返回false。
bool joinable() const noexcept;

赋值只能move资源,不能拷贝

// move (0k)	
thread& operator= (thread&& other) noexcept;
// copy (x)	
thread& operator= (const other&) = delete;

获取当前计算机的 CPU 核心数,根据这个结果在程序中创建出数量相等的线程,每个线程独自占有一个CPU核心,这些线程就不用分时复用CPU时间片,此时程序的并发效率是最高的。

static unsigned hardware_concurrency() noexcept;

使用例子:

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

void download1()
{
	// 模拟下载, 总共耗时500ms,阻塞线程500ms
	this_thread::sleep_for(chrono::milliseconds(500));
	cout << "download1: " << this_thread::get_id() << endl;
}

void download2()
{
	// 模拟下载, 总共耗时300ms,阻塞线程300ms
	this_thread::sleep_for(chrono::milliseconds(300));
	cout << "download2: " << this_thread::get_id() << endl;
}

void doSomething()
{
	cout << "doSomething" << endl;
}

int main()
{
	thread t1(download1);
	thread t2(download2);
	// 阻塞主线程,等待所有子线程任务执行完毕再继续向下执行
	t1.join();
	t2.join();
	doSomething();
	return 0;
}

结果:

download2: 4656
download1: 2780
doSomething

detach之后如果不模拟延时,线程应该来不及执行就结束了。

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

void download1()
{
	// 模拟下载, 总共耗时500ms,阻塞线程500ms
	this_thread::sleep_for(chrono::milliseconds(500));
	cout << "download1: " << this_thread::get_id() << endl;
}

void download2()
{
	// 模拟下载, 总共耗时300ms,阻塞线程300ms
	this_thread::sleep_for(chrono::milliseconds(300));
	cout << "download2: " << this_thread::get_id() << endl;
}

void doSomething()
{
	cout << "doSomething" << endl;
}

int main()
{
	thread t1(download1);
	thread t2(download2);
	// 阻塞主线程,等待所有子线程任务执行完毕再继续向下执行
	t1.detach();
	t2.detach();
	doSomething();
	// 让主线程休眠, 等待子线程执行完毕
	this_thread::sleep_for(chrono::seconds(5));
	return 0;
}

结果:

doSomething
download2: 16728
download1: 6676

this_thread命名空间

当前线程的线程 ID,基本上在线程函数中使用。

thread::id get_id() noexcept;

线程从运行态变成阻塞态并在这种状态下休眠一定的时长,因为阻塞态的线程已经让出了 CPU 资源,代码也不会被执行,所以线程休眠过程中对 CPU 来说没有任何负担。
程序休眠完成之后,会从阻塞态重新变成就绪态,就绪态的线程需要再次争抢 CPU 时间片,抢到之后才会变成运行态,这时候程序才会继续向下运行。

template <class Rep, class Period>
void sleep_for (const chrono::duration<Rep,Period>& rel_time);

线程阻塞到某一个指定的时间点之后解除阻塞

template <class Clock, class Duration>
void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);

运行态的线程主动让出自己已经抢到的 CPU 时间片,最终变为就绪态,进入下一轮 CPU 时间片抢夺。
避免一个线程长时间占用CPU资源,从而导致多线程处理性能下降。

void yield() noexcept;

互斥锁

互斥锁的个数和共享资源的个数相等,也就是说每一个共享资源都应该对应一个互斥锁对象。
互斥锁对象的个数和线程的个数没有关系。

std::mutex

最基本的互斥锁

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;

int g_num = 0;  // 为 g_num_mutex 所保护
mutex g_mutex;

void slow_increment(int id)
{
	for (int i = 0; i < 3; ++i)
	{
		g_mutex.lock();
		++g_num;
		cout << id << " => " << g_num << endl;
		g_mutex.unlock();

		this_thread::sleep_for(chrono::seconds(1));
	}
}

int main()
{
	thread t1(slow_increment, 0);
	thread t2(slow_increment, 1);
	t1.join();
	t2.join();
	return 0;
}

std::recursive_mutex

递归锁,允许同一线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
不建议使用,容易导致复杂的bug。

#include <iostream>
#include <thread>
#include <mutex>
 
std::recursive_mutex rmtx;
 
void recursive_function(int depth)
{
    rmtx.lock();
    std::cout << "Depth: " << depth << std::endl;
    if (depth > 0)
    {
        recursive_function(depth - 1);
    }
    rmtx.unlock();
}
 
int main()
{
    std::thread t(recursive_function, 5);
    t.join();
 
    return 0;
}

std::timed_mutex

带超时机制的互斥锁,在获取互斥锁资源时增加了超时等待功能,因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置了一个超时时长,超时后线程就可以解除阻塞去做其他事情了。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex mtx;  

void try_lock_function()
{
    if (mtx.try_lock_for(std::chrono::seconds(1)))
    {
        std::cout << "Lock acquired!\n";
        // 执行受保护的操作
        std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
        mtx.unlock(); // 显式解锁
    }
    else
    {
        std::cout << "Failed to acquire lock within timeout.\n";
    }
}

int main()
{
    std::thread t1(try_lock_function);
    std::thread t2(try_lock_function);

    t1.join();
    t2.join();

    return 0;
}

std::recursive_timed_mutex

带超时机制的递归互斥锁,支持递归锁定和超时机制。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::recursive_timed_mutex mtx;

void recursive_lock_function()
{
    if (mtx.try_lock_for(std::chrono::seconds(1)))
    {
        std::cout << "Lock acquired!\n";
          
        // 递归锁定
        if (mtx.try_lock_for(std::chrono::seconds(1)))
        {
            std::cout << "Recursive lock acquired!\n";
            mtx.unlock(); // 释放递归锁
        }
        else
        {
            std::cout << "Failed to acquire recursive lock within timeout.\n";
        }

        // ... 执行受保护的操作

        mtx.unlock(); // 释放原始锁
    }
    else
    {
        std::cout << "Failed to acquire lock within timeout.\n";
    }
}

int main()
{
    std::thread t1(recursive_lock_function);
    std::thread t2(recursive_lock_function);

    t1.join();
    t2.join();

    return 0;  
}

std::shared_mutex

C++14引入的,读写锁,许多个线程同时读取,但只有一个线程可以写入。这在读多写少的场景下非常有用。


#include <iostream>
#include <thread>
#include <shared_mutex>
 
std::shared_mutex shmtx;
 
void read_shared(int id)
{
    std::shared_lock<std::shared_mutex> lock(shmtx); // 共享锁
    std::cout << "Thread " << id << " is reading" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
 
void write_shared(int id)
{
    std::unique_lock<std::shared_mutex> lock(shmtx); // 独占锁
    std::cout << "Thread " << id << " is writing" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
 
int main()
{
    std::thread readers[5], writer(write_shared, 1);
 
    for (int i = 0; i < 5; ++i) {
        readers[i] = std::thread(read_shared, i + 2);
    }
 
    writer.join();
    for (auto& reader : readers) {
        reader.join();
    }
 
    return 0;
}

std::shared_timed_mutex

带超时机制的共享互斥锁。

#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>
 
std::shared_timed_mutex shtmmtx;
 
void try_read_shared(int id)
{
    if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100)))
    {
        std::cout << "Thread " << id << " is reading" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        shtmmtx.unlock_shared();
    }
    else
    {
        std::cout << "Thread " << id << " could not read" << std::endl;
    }
}
 
void try_write_shared(int id)
{
    if (shtmmtx.try_lock_for(std::chrono::milliseconds(100)))
    {
        std::cout << "Thread " << id << " is writing" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        shtmmtx.unlock();
    }
    else
    {
        std::cout << "Thread " << id << " could not write" << std::endl;
    }
}
 
int main()
{
    std::thread readers[5], writer(try_write_shared, 1);
 
    for (int i = 0; i < 5; ++i)
    {
        readers[i] = std::thread(try_read_shared, i + 2);
    }
 
    writer.join();
    for (auto& reader : readers)
    {
        reader.join();
    }
 
    return 0;
}

锁定策略

std::lock_guard

使用卫兵来上锁和解锁

// 类的定义,定义于头文件 <mutex>
template< class Mutex >
class lock_guard;

// 常用构造函数
explicit lock_guard( mutex_type& m );

通过修改发现代码被精简了,而且不用担心因为忘记解锁而造成程序的死锁,但是这种方式也有弊端,在上面的示例程序中整个for循环的体都被当做了临界区,多个线程是线性的执行临界区代码的,因此临界区越大程序效率越低,还是需要根据实际情况选择最优的解决方案。

void slow_increment(int id)
{
	for (int i = 0; i < 3; ++i)
	{
		lock_guard<mutex> lock(g_mutex);
		++g_num;
		cout << id << " => " << g_num << endl;
		this_thread::sleep_for(chrono::seconds(1));
	}
}

锁定策略:

  • defer_lock — 不获取互斥锁的所有权
  • adopt_lock — 假设调用线程已经拥有互斥锁的所有权
std::mutex m;
 
// 使用 defer_lock 策略
void using_defer_lock()
{
    std::lock_guard<std::mutex> guard(m, std::defer_lock);
    m.lock(); // 显式锁定
    // 互斥区...
    m.unlock(); // 显式解锁
}
 
// 使用 adopt_lock 策略
void using_adopt_lock()
{
    m.lock(); // 在进入互斥区之前锁定
    std::lock_guard<std::mutex> guard(m, std::adopt_lock);
    // 互斥区...
}

std::unique_lock

由于lock_guard使用简单,所以不够灵活。
unique_lock允许手动锁定和解锁互斥量,以及与条件变量一起使用。当对象离开作用域时,也会自动解锁互斥量。
策略类型:

  • adopt_lock
  • defer_lock
  • try_to_lock — 尝试在不阻塞的情况下获取互斥锁的所有权
// 使用 try_to_lock 策略
void using_try_to_lock()
{
    std::unique_lock<std::mutex> guard(m, std::try_to_lock);
    if (guard.owns_lock())
    {
        // 已经获得了锁
        // 互斥区...
    }
    else
    {
        // 锁已被占用,执行其他操作或重试
    }
}

std::shared_lock

C++14引入的锁,这是一个用于共享互斥量(如 std::shared_mutex 或 std::shared_timed_mutex)的锁,允许多个线程同时读取共享数据,但在写入数据时仍然保证互斥。

std::scoped_lock

C++17 引入的一个新锁,用于同时锁定多个互斥量,以避免死锁。

条件变量

另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用。

condition_variable 配合 unique_lock 使用更灵活一些,可以在任何时候自由地释放互斥锁,而 condition_variable_any 如果和 lock_guard 一起使用必须要等到其生命周期结束才能将互斥锁释放。但是,condition_variable_any 可以和多种互斥锁配合使用,应用场景也更广,而 condition_variable 只能和独占的非递归互斥锁(mutex)配合使用,有一定的局限性。

std::condition_variable

#include <condition_variable>

// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
// 在阻塞线程时候,如果线程已经对互斥锁 mutex 上锁,那么会将这把锁打开,这样做是为了避免死锁
// 当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个 mutex 互斥锁锁上,继续向下访问临界区
// 独占的互斥锁对象不能直接传递,需要通过模板类 unique_lock 进行二次处理
void wait (unique_lock<mutex>& lck);
// 第二个参数是一个判断条件,是一个返回值为布尔类型的函数
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);

// 假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。
template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time);
	
template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);

// 假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);

template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);

// 唤醒一个被当前条件变量阻塞的线程
void notify_one() noexcept;
// 唤醒全部被当前条件变量阻塞的线程
void notify_all() noexcept;

生产者消费者例子

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <functional>
#include <condition_variable>
using namespace std;

class SyncQueue
{
public:
	SyncQueue(int maxSize) : m_maxSize(maxSize) {}

	void put(const int& x)
	{
		unique_lock<mutex> locker(m_mutex);
		// 根据条件阻塞线程
		m_notFull.wait(locker, [this]() {
			return m_queue.size() != m_maxSize;
		});
		// 将任务放入到任务队列中
		m_queue.push_back(x);
		cout << x << " 被生产" << endl;
		// 通知消费者去消费
		m_notEmpty.notify_one();
	}


	int take()
	{
		unique_lock<mutex> locker(m_mutex);
		m_notEmpty.wait(locker, [this]() {
			return !m_queue.empty();
		});
		// 从任务队列中取出任务(消费)
		int x = m_queue.front();
		m_queue.pop_front();
		// 通知生产者去生产
		m_notFull.notify_one();
		cout << x << " 被消费" << endl;
		return x;
	}

	bool empty()
	{
		lock_guard<mutex> locker(m_mutex);
		return m_queue.empty();
	}

	bool full()
	{
		lock_guard<mutex> locker(m_mutex);
		return m_queue.size() == m_maxSize;
	}

	int size()
	{
		lock_guard<mutex> locker(m_mutex);
		return m_queue.size();
	}

private:
	list<int> m_queue;     // 存储队列数据
	mutex m_mutex;         // 互斥锁
	condition_variable m_notEmpty;   // 不为空的条件变量
	condition_variable m_notFull;    // 没有满的条件变量
	int m_maxSize;       // 任务队列的最大任务个数
};

int main()
{
	SyncQueue taskQ(50);
	auto produce = bind(&SyncQueue::put, &taskQ, placeholders::_1);
	auto consume = bind(&SyncQueue::take, &taskQ);
	thread t1[3];
	thread t2[3];
	for (int i = 0; i < 3; ++i)
	{
		t1[i] = thread(produce, i + 100);
		t2[i] = thread(consume);
	}

	for (int i = 0; i < 3; ++i)
	{
		t1[i].join();
		t2[i].join();
	}

	return 0;
}

std::condition_variable_any

可以和任意带有 lock()、unlock() 语义的 mutex 搭配使用,也就是说上面介绍的四种都可以。

template <class Lock> 
void wait (Lock& lck);

template <class Lock, class Predicate>
void wait (Lock& lck, Predicate pred);

template <class Lock, class Rep, class Period>
cv_status wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time);
	
template <class Lock, class Rep, class Period, class Predicate>
bool wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);

template <class Lock, class Clock, class Duration>
cv_status wait_until (Lock& lck, const chrono::time_point<Clock,Duration>& abs_time);

template <class Lock, class Clock, class Duration, class Predicate>
bool wait_until (Lock& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);

void notify_one() noexcept;
void notify_all() noexcept;

生产者消费者例子

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <functional>
#include <condition_variable>
using namespace std;

class SyncQueue
{
public:
    SyncQueue(int maxSize) : m_maxSize(maxSize) {}

    void put(const int& x)
    {
        lock_guard<mutex> locker(m_mutex);
        // 根据条件阻塞线程
        m_notFull.wait(m_mutex, [this]() {
            return m_queue.size() != m_maxSize;
        });
        // 将任务放入到任务队列中
        m_queue.push_back(x);
        cout << x << " 被生产" << endl;
        // 通知消费者去消费
        m_notEmpty.notify_one();
    }

    int take()
    {
        lock_guard<mutex> locker(m_mutex);
        m_notEmpty.wait(m_mutex, [this]() {
            return !m_queue.empty();
        });
        // 从任务队列中取出任务(消费)
        int x = m_queue.front();
        m_queue.pop_front();
        // 通知生产者去生产
        m_notFull.notify_one();
        cout << x << " 被消费" << endl;
        return x;
    }

    bool empty()
    {
        lock_guard<mutex> locker(m_mutex);
        return m_queue.empty();
    }

    bool full()
    {
        lock_guard<mutex> locker(m_mutex);
        return m_queue.size() == m_maxSize;
    }

    int size()
    {
        lock_guard<mutex> locker(m_mutex);
        return m_queue.size();
    }

private:
    list<int> m_queue;     // 存储队列数据
    mutex m_mutex;       // 互斥锁
    condition_variable_any m_notEmpty;   // 不为空的条件变量
    condition_variable_any m_notFull;    // 没有满的条件变量
    int m_maxSize;       // 任务队列的最大任务个数
};

int main()
{
    SyncQueue taskQ(50);
    auto produce = bind(&SyncQueue::put, &taskQ, placeholders::_1);
    auto consume = bind(&SyncQueue::take, &taskQ);
    thread t1[3];
    thread t2[3];
    for (int i = 0; i < 3; ++i)
    {
        t1[i] = thread(produce, i + 100);
        t2[i] = thread(consume);
    }

    for (int i = 0; i < 3; ++i)
    {
        t1[i].join();
        t2[i].join();
    }

    return 0;
}

原子操作

原子操作是不可打断的最低粒度操作,是线程安全的。

对原子变量的操作可以分为三种:

  • store 将一个值存到原子变量中。
  • load 读取原子变量中的值。
  • read-modify-write (RMW) 原子地执行读取,修改和写入。如自增操作 fetch_add,交换操作 exchange (返回变量当前的值并写入指定值) 等。

Happens-before 是一个非常重要的概念,如果操作 a “happens-before” 操作 b, 则操作 a 的结果对于操作 b 可见。happens-before 的关系可以建立在用一个线程的两个操作之间,也可以建立在不同的线程的两个操作之间。

内存序:

  • memory_order_relaxed — 宽松模型,不对执行顺序做保证
  • memory_order_consume — 当前线程中,满足happens-before原则,当前线程中该原子的所有后续操作,必须在本条操作完成之后执行
  • memory_order_acquire — 当前线程中,读操作满足happens-before原则,所有后续的读操作必须在本操作完成后执行
  • memory_order_release — 当前线程中,写操作满足happens-before原则,所有后续的写操作必须在本操作完成后执行
  • memory_order_acq_rel — 当前线程中,同时满足memory_order_acquire和memory_order_release
  • memory_order_seq_cst — 最强约束,全部读写都按顺序执行

原子类有:

  • std::atomic_flag
  • std::atomic
  • std::atomic整数指针特化

实现一个锁:

struct LFLock
{
    std::atomic_flag flag;

    LFLock() : flag{false}
    {
    }

    void lock()
    {
        while (flag.test_and_set(std::memory_order_acquire)) ;
    }

    bool try_lock()
    {
        return !flag.test_and_set(std::memory_order_acquire);
    }
    
    void unlock()
    {
        flag.clear(std::memory_order_release);
    }
};

std::future

提供了一种机制,允许我们在多线程环境中安全地获取异步操作的结果,无需关注线程同步和通信的具体实现细节。

使用std::async关联异步任务,创建并运行一个异步任务,并返回一个与该任务结果关联的std::future对象。
std::future::get()函数只能被调用一次。

#include <iostream>
#include <future>
#include <chrono>

int long_running_task()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 42;
}

int main()
{
    std::future<int> result_future = std::async(std::launch::async, long_running_task);

    // 在此处执行其他操作

    int result = result_future.get();
    std::cout << "Result: " << result << std::endl;

    return 0;
}

使用std::promise与std::future配合,std::promise对象来显式地设置任务的结果,而std::future对象则用于获取这个结果。

#include <iostream>
#include <future>
#include <thread>

void long_running_task(std::promise<int> result_promise)
{
    // 执行长时间运行的任务
    int result = 42;

    // 将结果设置到promise对象中
    result_promise.set_value(result);
}

int main()
{
    std::promise<int> result_promise;
    std::future<int> result_future = result_promise.get_future();

    // 创建一个新线程,执行长时间运行的任务
    std::thread task_thread(long_running_task, std::move(result_promise));

    // 在此处执行其他操作

    int result = result_future.get();
    std::cout << "Result: " << result << std::endl;

    task_thread.join();

    return 0;
}

为了避免阻塞,我们可以使用std::future::wait_for()或std::future::wait_until()函数等待一段时间或直到某个时刻。这些函数会返回一个std::future_status枚举值,表示异步操作的状态

  • std::future_status::ready
  • std::future_status::timeout
  • std::future_status::deferred
#include <iostream>
#include <future>
#include <chrono>
#include <thread>

void long_running_task(std::promise<int> result_promise)
{
    // 执行长时间运行的任务
    std::this_thread::sleep_for(std::chrono::seconds(3));
    int result = 42;
    // 将结果设置到promise对象中
    result_promise.set_value(result);
}

int main()
{
    std::promise<int> result_promise;
    std::future<int> result_future = result_promise.get_future();

    // 创建一个新线程,执行长时间运行的任务
    std::thread task_thread(long_running_task, std::move(result_promise));

    while (true)
    {
        // 在此处执行其他操作
        std::this_thread::sleep_for(std::chrono::seconds(1));

        std::chrono::milliseconds timeout(1);
        std::future_status status = result_future.wait_for(timeout);

        if (status == std::future_status::ready)
        {
            int result = result_future.get();
            std::cout << "Result: " << result << std::endl;
            break;
        }
        else
        {
            std::cerr << "Timeout: the task is still running" << std::endl;
        }
    }

    task_thread.join();

    return 0;
}

更多推荐