c++多线程入门(2)

本文介绍了C++多线程入门的一些关键概念,包括如何向线程函数传递参数,避免悬空引用问题,以及如何通过unique_ptr实现动态参数传递。此外,还探讨了线程归属权的转移,如使用move操作和实现类似C++20 jthread的joining_thread类。最后,提供了一个并行版的accumulate函数实战示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一: 向线程函数传递参数

thread的构造函数里第一个参数为调用对象, 从第二个参数起, 为线程函数的第一个参数

//若用cout<< *** <<endl输出可能会乱序
void func(int i,string s)
{
    printf("i=%d,s=%s",i,s.c_str());
}
int main()
{
    int i=1;
    char s[]="test";

    //第一个参数为调用对象, 从第二个参数起, 为线程函数的第一个参数
    thread t1(func,i,s);
    t1.detach();
}

----------------------------

线程内部有存储空间, 参数会按照默认方式复制到该处, 新创建的线程才能直接访问他们

上面的代码数组s以char const*的形式传入, 进入线程的上下文环境以后, 才转换为string类型

但有问题:

        可能没转换之前, 主线程已经退出,并销毁s, 造成子线程对s的悬空引用

一个解决办法如下: 加上string(s)保证在传进参数之前已经转化为string对象

//若用cout<< *** <<endl输出可能会乱序
void func(int i,const string &s)
{
    printf("i=%d,s=%s",i,s.c_str());
}
int main()
{
    int i=1;
    char s[]="test";

    //第一个参数为调用对象, 从第二个参数起, 为线程函数的第一个参数
    //加上string(s)保证在传进参数之前已经转化为string对象
    thread t1(func,i,string(s));
    t1.detach();
}

-----------------------

考虑另一种情形, 参数需要非const引用, 而不复制对象, 以下代码编译失败

void func(int &i)
{
    printf("i=%d");
}
int main()
{
    int i=1;
    //编译失败
    thread t1(func,i);
    t1.join();
}

原因: thread构造函数对func不知情, 会把参数直接复制, 之后以右值的形式传递, 最后func函数会收    到一个右值作为参数, 编译失败

解决: 调用ref函数包装

void func(int &i)
{
    printf("i=%d");
}
int main()
{
    int i=1;
    //编译通过
    thread t1(func,ref(i));
    t1.join();
}

-----------

另一种传递参数的方法: 通过unique传递动态参数

如下

class A
{
public:
    int i;
    A(int i) : i(i)
    { printf("constructor\n"); }
    void func()
    {
        cout << "in A::func()" << endl;
    }
};

void func(unique_ptr<A> p)
{
    printf("i=%d", p->i);
}
int main()
{
    unique_ptr<A> p(new A(1));

    //通过move把对象转移给线程,此时不额外创建新对象
    thread t1(func, move(p));
    t1.join();
}

 二: 移交线程归属权

书中的例子

void func1()
{
    printf("in func1");
}
void func2()
{
    printf("in func2");
}
int main()
{
    thread t1(func1);
    thread t2 = move(t1);  //线程t1归属权转为t2
    cout<< t1.joinable(); // 输出0
    
    t1 = thread(func2);  //启动另一新线程, 随即归属权转移为t1
    
    thread t3;
    t3 = move(t2);  //归属权t2->t3

    //出错, 此时t1已关联运行func2()函数的线程, terminate()会被调用, 程序终结
    t1 = move(t3); 

    //省略调用join()
}

可以得到:

1可以通过move()函数将线程归属权移出

2可以通过=号获取新创建的线程归属权

3当对象已经关联有线程时, 不可用关联另一线程(通过move()和通过=号都不可以)

thread支持移动操作, 函数可以向外传递线程, 也可以向函数传递线程

//接收thread参数
void func2(thread t)
{
    printf("in func2");
    t.join();
}

//从内部返回thread对象
thread get_thread()
{
    thread t(func1);
    return t;
}
int main()
{
    thread t=get_thread();//获取thread对象
    t.join();

    func2(thread(func1));//传递thread参数
}

------------------

在上一篇里介绍了thread_guard类, 即对象销毁时自动调用内部线程对象join()函数, 再加上支持归属权移动功能, 实现一个可行的joining_thread类(仿造c++20的jthread类)

class joining_thread
{
    thread t;
public:
    joining_thread() noexcept = default;

    template<class Callable, class ...Args>
    explicit joining_thread(Callable &&func, Args &&...args1):
            t(forward<Callable>(func), forward<Args>(args1)...)
    {}
    
    explicit joining_thread(thread t_) noexcept: t(move(t_))
    {}
    
    joining_thread(joining_thread &&other) noexcept:
            t(move(other.t))
    {}
    
    joining_thread &operator=(joining_thread &&other) noexcept
    {
        if (joinable())
        {
            join();
        }
        t = move(other.t);
        return *this;
    }
    joining_thread &operator=(thread &&other) noexcept
    {
        if (joinable())
        {
            join();
        }
        t = move(other);
        return *this;
    }

    ~joining_thread() noexcept
    {
        if (joinable())
        {
            join();
        }
    }
    void swap(joining_thread &other) noexcept
    {t.swap(other.t);}

    thread::id get_id() const noexcept
    {return t.get_id();}

    bool joinable() const noexcept
    {return t.joinable();}

    void join() noexcept
    {t.join();}

    void detach() noexcept
    {t.detach();}

    const thread &as_thread() const noexcept
    {return t;}
};

三: 并行版的accumulate完整实现

算是第一个多线程编程实战

#include "iostream"
#include "thread"
#include "vector"
#include "numeric" //accumulate在此头文件
using namespace std;

template<typename Iterator,typename T>
struct accumulate_block
{
    void operator()(Iterator first,Iterator last,T&result)
    {
        result= accumulate(first,last,result);
    }
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
    int length= distance(first,last);
    if(length==0)return init;

    //确定需要多少线程合适, hardware_concurrency为可真正并发的线程数量
    int threads_num=min(length/20-1,(int)thread::hardware_concurrency());
    //若length太小, 则用2个子线程
    threads_num=max(2,threads_num);

    int block_size=length/threads_num;//确定每个线程处理的block_size
    vector<T> results(threads_num); //存储各个线程暂存的结果(包括主线程)
    vector<thread> threads(threads_num-1);//主线程算一个, 因此少创建1个子线程

    Iterator block_first=first;
    int i=0;
    for(i=0 ;i <(threads_num-1); i++)
    {
        Iterator block_end=block_first;
        advance(block_end,block_size);  //advance函数将block_end向前移block_size个大小
        threads[i]=thread(accumulate_block<Iterator,T>(),block_first,block_end,ref(results[i]));
        block_first=block_end;
    }

    accumulate_block<Iterator,T>()(block_first,last,results[threads_num-1]);//最后一段block由主线程处理
    for(auto &entry:threads)
    {
        entry.join();
    }
    return accumulate(results.begin(),results.end(),init);
}

int main()
{
    vector a={1,2,3,4,5,6,7,8,9,10};
    cout<< parallel_accumulate(a.begin(),a.end(),0) <<endl;//输出55
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值