C++学习之路(01)—多线程std::thread初探

目录

0.前言

1.线程与进程的概念

2.C++11后的多线程

2.1基础使用

2.2小技巧

3.线程安全

3.1互斥锁

3.2原子操作(atomic)


0.前言

由于是学习笔记,就不放在项目经验系列里了。

参考资料:

C++多线程详细讲解_千场托儿索的博客-CSDN博客_c++多线程

https://www.jianshu.com/p/5f0bc41249ad

1.线程与进程的概念

废话不多说。

进程就是程序的实体,比如你打开任务管理器,上面vscode,wegame等等就是应用,一个应用可能包含一个或多个进程,各司其职。一般而言,进程之间是分隔开的,一个崩了不影响其他的,除非进程之间存在通信,那就是多进程编程,但这样有诸如效率低,麻烦等缺点。

而线程就是轻量级的进程,区别在于线程不独立的拥有资源,依赖于创建它的进程而存在,同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据。而一个进程至少拥有一个线程。

应用-进程-线程,就像公司-部门-员工吧。

2.C++11后的多线程

2.1基础使用

之前的pthread.h太麻烦,C++11后,就引入了#include<thread>,它在CMakeLists中的引用方式为

find_package(Threads REQUIRED)

target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})

创建和使用多线程的方式如下

#include <iostream>
#include <thread>

int k=0;

void foo(int n)
{
for(int i=0;i<n;i++)
{k++;}
}

void foo_2(int n)
{
for(int i=0;i<n;i++)
{k++;}
}

int main()
{
    std::thread Tr1(foo,100);
    std::thread Tr2(foo_2,100);

    Tr1.join();
    Tr2.join();

    std::cout<<k<<std::endl;
}

其中,当创建好std::thread对象时,函数foo和foo_2就已经在执行了,下面的join的意思是,如果当main()函数执行到这里,Tr1和Tr2还在执行,就等等,执行完了才往下走(很合理,线程阻塞,等待结果),detach不推荐使用。

然后这里相当于有两个线程在同时执行foo和foo_2函数的,这就提高了效率。

实际写程序,可能遇到如下报错,原因是函数名可能和某个类的成员函数名重合了,成员函数作为传参需加上类名。

error: no matching function for call to 'std::thread::thread ...

然后这里还有个线程安全的问题,即,上面两个线程同时在修改公共资源k,当你把n调大,比如100000,就会发现k不等于2n。

2.2小技巧

我现在有一段代码,如果我不想专门创建一个函数呢?没关系,可以用匿名函数

std::thread Tr([&](){some codes});

即便这个some codes有千行也可以。

第二个是,我不确定我要创建几个线程,能不能动态决定?

可以,用数组和匿名对象

std::vector<std::thread> TRs;
TRs.reserve(num);

for(int i=0;i<num;i++)
{
    TRs.emplace_back(std::thread([&,=i](){some codes})); // 公共资源要么上锁,要么像i一样使用值传递
}

for(int i=0;i<num;i++)
{
    TRs[i].join();
}

最后问一下,一个进程能创建多少线程?跟默认预留堆栈空间有关,linux下,大约几百到几千吧。

3.线程安全

3.1互斥锁

可以通过对资源上锁的方式,杜绝同时访问,高级的我还不懂,初级的手动锁和自动锁如下:

①手动上锁解锁

// 手动锁
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int k=0;

void foo(int n)
{
mtx.lock();
for(int i=0;i<n;i++)
{k++;}
mtx.unlock();
}

void foo_2(int n)
{
mtx.lock();
for(int i=0;i<n;i++)
{k++;}
mtx.unlock();
}

int main()
{
    std::thread Tr1(foo,100);
    std::thread Tr2(foo_2,100);

    Tr1.join();
    Tr2.join();

    std::cout<<k<<std::endl;
}

②自动锁

// 自动锁
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int k=0;

void foo(int n)
{
std::lock_guard<std::mutex> lock(mtx);
for(int i=0;i<n;i++)
{k++;}
}

void foo_2(int n)
{
std::lock_guard<std::mutex> lock(mtx);
for(int i=0;i<n;i++)
{k++;}
}

int main()
{
    std::thread Tr1(foo,100);
    std::thread Tr2(foo_2,100);

    Tr1.join();
    Tr2.join();

    std::cout<<k<<std::endl;
}

然后需要注意的是,我们说的同时访问指的是真的同一处,比如你定义一个公共资源结构体,两个线程同时访问同一结构体实例中的不同成员,其实是没有影响的(个人实验,不对请指教)。

3.2原子操作(atomic)

互斥锁虽然保证了安全,但性能开销挺大的,所以C++11引入了原子,即,直接把公共资源设为原子类型,它会自动保证同时只有一个线程访问修改,不需要加锁解锁,代码如下

// 原子
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>

std::atomic<int> k=0;

void foo(int n)
{
for(int i=0;i<n;i++)
{k++;}
}

void foo_2(int n)
{
for(int i=0;i<n;i++)
{k++;}
}

int main()
{
    std::thread Tr1(foo,100);
    std::thread Tr2(foo_2,100);

    Tr1.join();
    Tr2.join();

    std::cout<<k<<std::endl;
}

当然原子的玩法有很多,暂时没探究了,总而言之,它的性能开销比互斥锁加锁解锁小,也能保证安全。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值