Bootstrap

c++多线程

lambda的简单说明

lambda的格式:
[捕获列表]<模板声明>(参数列表)mutable 异常说明->类型{函数体}

auto b = [](int a, int b) { return a + b; };

[]<class T>(T v) noexcept -> void{
    cout << v << endl;
}("blog.csdn.net");
//捕获列表 函数体必写 其他可省略 // 最短的lambda函数
auto b = [] { return 1; };

在这里插入图片描述


1.主线程在 t.join() 处阻塞,等待t线程执行完毕

2.std::thread::joinable() 用于检查 std::thread 是否可联结(joinable)joinable() 返回 false:线程对象未关联线程(未初始化),或线程已被 join()detach()。 joinable()返回true:线程对象关联了一个有效的线程,且尚未 join()detach()。


demo1 函数指针

thread t = thread(printMessage, 5);这行代码会立即启动一个新线程,执行传入的方法。这里和java不一样

#include<iostream>
#include<thread>
using namespace std;
void printMessage(int count) {
    for (int i = 0; i < count; ++i) {
        std::cout << "Hello from thread (function pointer)!\n";
    }
}
int main()
{

    thread t = thread(printMessage, 5);
    t.join();// 等待线程完成
    return 0;
}
demo2 仿函数 (函数对象)

使用对象的operator方法

#include <iostream>
#include <thread>

class PrintTask {
public:
    void operator()(int count) const {
        for (int i = 0; i < count; ++i) {
            std::cout << "Hello from thread (function object)!\n";
        }
    }
};

int main() {
    std::thread t2(PrintTask(), 5); // 创建线程,传递函数对象和参数
    t2.join(); // 等待线程完成
    return 0;
}
demo3 lambda表达式

使用lambda

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

int main()
{
    thread t( 
        [](int count){
            for (int i = 0; i < count; ++i) {
                        std::cout << "Hello from thread (lambda)!\n";
                    }
        },
        5
    );
    t.join();
    return 0;
}
std::mutex

std::mutex是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int n = 0;
mutex mtx;// 全局互斥量
void count10000() {
	for (int i = 1; i <= 10000; i++) {
		mtx.lock(); // 请求锁定互斥量
		n++;
		mtx.unlock();// 释放互斥量
	}
}
int main() {
	thread th[10];
	for (thread &x : th)
		x = thread(count10000);
	for (thread &x : th)
		x.join();
	cout << n << endl;
	return 0;
}

还有一个bool try_lock()。尝试将mutex上锁。如果mutex未被上锁,则将其上锁并返回true;如果mutex已被锁则返回false。

std::atomic

原子操作确保对共享数据的访问是不可分割的,即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
std::atomic<int> n = 0;
void count10000() {
	for (int i = 1; i <= 10000; i++) {
		n++;
	}
}
int main() {
	thread th[100];
	for (thread &x : th)
		x = thread(count10000);
	for (thread &x : th)
		x.join();
	cout << n << endl;
	return 0;
}
thread_local

线程局部存储允许每个线程拥有自己的数据副本。这可以通过thread_local关键字实现,避免了对共享资源的争用。

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

thread_local int n = 0;
mutex mtx; // // 加锁只是保护输出

int main(int argc, char const *argv[])
{
    thread t1 =thread(
        []() {
            n= 1;
            mtx.lock();
            std::cout<<"thread1" << n << std::endl;
            mtx.unlock();
        }
    );
    thread t2 =thread(
        []() {
            n= 2;
            mtx.lock();
            std::cout <<"thread2" << n << std::endl;
            mtx.unlock();
        }
    );
     thread t3 =thread(
        []() {
             mtx.lock();
            std::cout <<"thread3" << n << std::endl;
             mtx.unlock();
        }
    );


    t1.join();
    t2.join();
    t3.join();
    return 0;
}
async
创建

template<class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);

可以创建有返回结果的线程

thread 也是可以同步或异步的,异步detach():将线程与主线程分离,使其独立执行。同步join():让主线程等待该线程完成。

// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
// #include <thread> // 这里我们用async创建线程
#include <future> // std::async std::future
using namespace std;

template<class ... Args> decltype(auto) sum(Args&&... args) {
	// C++17折叠表达式
	// "0 +"避免空参数包错误
	return (0 + ... + args);
}

int main() {
	// 注:这里不能只写函数名sum,必须带模板参数
	future<int> val = async(launch::async, sum<int, int, int>, 1, 10, 100);
	// future::get() 阻塞等待线程结束并获得返回值
	cout << val.get() << endl;
	return 0;
}

std::launch有2个枚举值和1个特殊值:

枚举值:launch::async异步启动
枚举值:launch::deferred在调用future::get、future::wait时同步启动(std::future见后文)
特殊值:launch::async | launch::defereed同步或异步,根据操作系统而定
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
 
using namespace std::chrono;
 
std::string fetchDataFromDB(std::string recvData) {
  //确保函数要5秒才能执行完成
  std::this_thread::sleep_for(seconds(5));
 
  //处理创建数据库连接、获取数据等事情
  return "DB_" + recvData;
}
 
std::string fetchDataFromFile(std::string recvData) {
  //确保函数要5秒才能执行完成
  std::this_thread::sleep_for(seconds(5));
 
  //处理获取文件数据
  return "File_" + recvData;
}
 
int main() {
  //获取开始时间
  system_clock::time_point start = system_clock::now();
 
  std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
 
  //从文件获取数据
  std::string fileData = fetchDataFromFile("Data");
 
  //从DB获取数据
  //数据在future<std::string>对象中可获取之前,将一直阻塞
  std::string dbData = resultFromDB.get();
 
  //获取结束时间
  auto end = system_clock::now();
 
  auto diff = duration_cast<std::chrono::seconds>(end - start).count();
  std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
 
  //组装数据
  std::string data = dbData +  " :: " + fileData;
 
  //输出组装的数据
  std::cout << "Data = " << data << std::endl;
 
  return 0;
}

只使用了5秒,而不是10秒

执行结果

对于执行结果:

我们可以使用get、wait、wait_for、wait_until等待执行结束

get:

get可以获得执行的结果。如果选择异步执行策略,调用get时,如果异步执行没有结束,get会阻塞当前调用线程,直到异步执行结束并获得结果,如果异步执行已经结束,不等待获取执行结果;

如果选择同步执行策略,只有当调用get函数时,同步调用才真正执行,这也被称为函数调用被延迟。

任务的结果只能被获取一次。再次调用会导致异常 std::future_error

wait:

功能:等待异步任务完成。阻塞当前线程,直到任务完成。不会返回任务结果。结果可以通过 get() 获取。

wait_for()

功能:等待指定的时间,检查任务是否完成。

wait_until()

功能:等待直到指定的时间点,检查任务是否完成。

等待行为:阻塞到指定的时间点,如果任务在这段时间内完成则返回;否则返回超时状态。

返回值:返回 std::future_status 枚举值,和 wait_for() 相同。

//查询future的状态
std::future_status status;
do {
    status = future.wait_for(std::chrono::seconds(1));
    if (status == std::future_status::deferred) {
        std::cout << "deferred" << std::endl;
    } else if (status == std::future_status::timeout) {
        std::cout << "timeout" << std::endl;
    } else if (status == std::future_status::ready) {
        std::cout << "ready!" << std::endl;
    }
} while (status != std::future_status::ready); 

std::future获取结果的方式有三种:

  • get:等待异步操作结束并返回结果
  • wait:等待异步操作结束,但没有返回值
  • waite_for:超时等待返回结果,还没执行完。上面示例中就是对超时等待的使用展示
注意事项

错误示范:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>
#include <iomanip>
#include <vector>

void task(int i) {
    std::cout << "Task"<<i<< "started" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Task"<<i<< "finished" << std::endl;


    auto end_time = std::chrono::system_clock::now();
    std::time_t end_time_t = std::chrono::system_clock::to_time_t(end_time);
    std::cout << "Task"<<i<< "finished time: " << std::put_time(std::localtime(&end_time_t), "%F %T") << std::endl;
}

int main() {
    // 记时
    auto start_time = std::chrono::system_clock::now();
    std::time_t start_time_t = std::chrono::system_clock::to_time_t(start_time);
    std::cout << "main Start time: " << std::put_time(std::localtime(&start_time_t), "%F %T") << std::endl;
    
   // std::vector<std::future<void>> mfus;
    for (size_t i = 0; i < 10; i++)
    {
         //mfus.push_back( std::async(std::launch::async, task,i));
         std::async(std::launch::async, task,i);
    }
    

    auto end_time = std::chrono::system_clock::now();
    std::time_t end_time_t = std::chrono::system_clock::to_time_t(end_time);
    std::cout << " main End time: " << std::put_time(std::localtime(&end_time_t), "%F %T") << std::endl;

    return 0;
}

这10个task 会按照顺序同步执行

为什么呢?

调用了 std::async 但是没有保存返回的 std::future 对象。当一个 std::future 对象在其生命周期结束时(例如,当它离开作用域或被显式销毁时),如果它关联的任务还没有完成,那么这个 std::future 对象的析构函数会等待任务完成。

std::future 它们会在循环体的末尾被销毁,从而导致每个任务实际上是依次等待完成的。

修改就是把上面的注释打开,使用vector不让future对象去销毁

这样就可以异步了,最好的话也可以加上下面的 ,虽然是void

   // Wait for all tasks to complete
    for (auto& fut : futures) {
        fut.get();  // This will block until the task is done
    }
;