C++ 多线程笔记1 线程的创建

发布于:2024-05-11 ⋅ 阅读:(79) ⋅ 点赞:(0)

里面代码会用到的头文件

#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <vector>
#include <stdlib.h>
#include <cmath>
#include <chrono>
#include <ctime>

入门例子

void mytest()
{
    std::cout<<"hello"<<'\n';
}
int main()
{
    std::thread t(mytest); //开启一个线程
    std::cout<<"hello world\n";
    t.join(); //终结线程
  
}
/*输出
hello world
hello

*/
  • 1.上面的代码使用了std::thread t,这行代码创建了一个新的线程t,并让它执行mytest函数。当这行代码执行时,mytest函数会在新创建的线程上开始执行。主线程(即创建t的线程)则会继续执行下一行代码。

  • 2.t.join()是一个阻塞调用,意味着主线程会等待,直到线程t(即mytest函数)执行完毕。换句话说,t.join()确保了mytest函数在新线程上完成执行后,主线程才会继续执行后续的代码。如果没有t.join() ,主线程可能在mytest函数完成之前结束,这可能会导致未定义的行为,因为当主线程结束时,所有其他线程都会被强制终止,即使它们还没有完成它们的任务。

利用多线程去计算

多线程可以加快数组元素的计算,例如,我们先创建一个数组,来进行普通的计算,顺便测试一下运行速度。

//一个简单计算的例子
double cf(double v)
{
    if (v<=0)
    return v;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));//让线程停下来1毫秒
    return sqrt((v * v+std::sqrt((v-5)*(v+2.5) )/2.0 )/v); //随便的计算
}

std::vector<double> vds;
    for(int i=0;i<1000;++i)
    vds.push_back(rand()); //随机赋值
    std::cout<<vds.size()<<'\n';  //大小
    double value=0.0;
auto nowc = clock();//现在的时间
    for(auto& info :vds)
    {
        value += cf(info);
    }   
    std::cout<<"value: "<<value<<"spend time is "<<clock()-nowc<<'\n';

输出内容

1000
value: 120539spend time is 16760

接下来,我们使用多线程来计算一下数组元素,我们让主线程计算一半数组元素,开一个线程计算另外一半数组元素。
在多线程中

//这个函数是给数组中从Begin位置到End位置中元素进行计算
template<class T,typename Fun>
double visit(std::thread::id id,T iterBegin,T iterEnd,Fun f)
{
   auto mainThreadId = std::this_thread::get_id();//获取主线程id
   if(id == mainThreadId)
    std::cout<<"This is MAIN Thread \n";
    else
    std::cout<<"This is WORK Thread \n";

    double v=0;
    for(auto iter=iterBegin; iter!=iterEnd;++iter)
    v+=f(*iter);
    return v;
}

 auto iter = vds.begin() +(vds.size()/2);//数组的后一半
    double anotherv = 0.0;
    auto iterEnd = vds.end();
    nowc = clock();
    std::thread s( //这里的mainThreadId
        [&anotherv,iter,iterEnd,mainThreadId]()
        {
            anotherv=visit(mainThreadId,iter,iterEnd,cf);//将当前线程id,计算数组后半的数据
        });
    auto halfv = visit(mainThreadId,vds.begin(),iter,cf);  //计算数组前半的数据
    s.join();
    std::cout<<"halfv+anotherv: "<<(halfv+anotherv)<<"spend time is "<<clock()-nowc<<'\n';  

输出内容

This is MAIN Thread
This is WORK Thread
halfv+anotherv: 120539spend time is 7890

由此可见,用多线程的技术后,计算速度比普通计算快接近一半.我们还可以建立三线程等更多线程来计算

//三线程代码
auto mainThreadId = std::this_thread::get_id();//获取主线程id

    std::vector<double> vds;
    for(int i=0;i<300;++i)
    vds.push_back(rand());
    std::cout<<vds.size()<<'\n';
    double value=0.0;
    
    // 让我们测试一下运行速度
    auto nowc = clock();//现在的时间
    for(auto& info :vds)
    {
        value += cf(info);
    }   
    std::cout<<"value: "<<value<<" spend time is "<<clock()-nowc<<'\n';

    auto begin1 = vds.begin();
    auto iter = vds.begin() +(vds.size()/3);
    auto iter2 = iter+(vds.size()/3);

    double anotherv = 0.0;
    double anotherv2 = 0.0;
    auto iterEnd = vds.end();
    nowc = clock();
    std::thread s( //这里的mainThreadId
        [&anotherv,begin1,iter,mainThreadId]()
        {
            anotherv=visit(mainThreadId,begin1,iter,cf);//将当前线程id,计算数组后半的数据
        }); 
     std::thread s2( //这里的mainThreadId
        [&anotherv2,iter,iter2,mainThreadId]()
        {
            anotherv2=visit(mainThreadId,iter,iter2,cf);//将当前线程id,计算数组后半的数据
        });
            
    auto halfv = visit(mainThreadId,iter2,vds.end(),cf);  //计算数组前半的数据
    s.join();
    s2.join();
    std::cout<<"halfv+anotherv: "<<(halfv+anotherv+anotherv2)<<" spend time is "<<clock()-nowc<<'\n';  
/*
输出
300
value: 36321.9 spend time is 4734
This is MAIN Thread
This is WORK Thread
This is WORK Thread
halfv+anotherv: 36321.9 spend time is 1582
*/

关于线程中的 mainThreadId还有几点,有以下几点需要澄清:

  • 线程 ID 的唯一性:1std::this_thread::get_id() 1返回调用线程的唯一标识符。因此,主线程和任何新创建的线程都会有不同的 ID。即使您在主线程中和新线程中都调用了 std::this_thread::get_id() 并将其赋值给 mainThreadId,这两个变量也会持有不同的值,因为它们是不同线程的 ID。

  • 捕获列表的作用:在 s 线程的捕获列表中,1mainThreadId 是通过引用捕获的1。这意味着 s 线程中的 mainThreadId 是 main 函数中 mainThreadId 的一个引用。但是,这并不意味着 s 线程中的 mainThreadId 就是主线程的 ID。它只是 main 函数中定义的同一个变量的引用。

  • 变量的生命周期:只要 main 函数还在执行,并且 mainThreadId 还在其作用域内,s 线程就可以通过其捕获的引用访问 mainThreadId。但是,一旦 main 函数结束,mainThreadId 的生命周期也就结束了,此时对 s 线程中捕获的引用的访问将是未定义的。