C++(三)–多线程

线程是进程的组成单位。不管在windows或者linux操作系统上,进程都是操作系统资源申请、调度和独立运行的基本单位一个程序可以有多个进程,例如可以同时打开多个程序的进程。另外,真正执行代码的是线程,进程只是线程的容器(执行环境)。
多线程的实现能够提高执行效率。

参考:《VC++深入详解》----孙鑫

目录

  • 一、 多线程的概念
    • 1.1 线程
    • 1.2 单线程和多线程
  • 二、C++ 11 多线程的实现
  • 2.1 thread类的定义
  • 2.2 多线程简单的实例
  • 2.3 互斥锁
    • 2.3.1 使用std::mutex
    • 2.3.2 使用std::lock_guard()
    • 2.3.3 使用std::unique_lock()
  • 2.4 原子操作

一、 多线程的概念

1.1 线程

线程主要由两个部分组成,一个是线程内核对象,操作系统用它来实施线程管理;另一个是线程栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。线程会在进程环境中创建,系统从进程的地址空间中分配内存,供线程的使用。

另外线程只有一个内核对象和一个栈,需要的内存很少,开销也比进程少;所以在编程中,往往用多线程来解决问题,尽量避免创建新的进程。

1.2 单线程和多线程

对于单线程来说,进程中只有一个线程在运行。比如一个病人需要动手术,医院安排了一位医生为他动手术,这个医生就是主线程,仅仅由这个主线程完成任务;对于多线程而言,进程中有多个线程,其中有一个是主线程,就好比在上述例子中医生多了几个护士,由医生和几个护士共同完成手术过程,那么手术的效率就高了。

二、C++ 11 多线程的实现

要基于C++ 11 实现多线程,需要包含头文件 < thread >。

2.1 thread类的定义

class thread{    // class for observing and managing threads
public:class id;typedef void *native_handle_type;thread() _NOEXCEPT{    // construct with no thread_Thr_set_null(_Thr);}template<class _Fn,class... _Args,class = typename enable_if<!is_same<typename decay<_Fn>::type, thread>::value>::type>explicit thread(_Fn&& _Fx, _Args&&... _Ax){ // construct with _Fx(_Ax...)_Launch(&_Thr,_STD make_unique<tuple<decay_t<_Fn>, decay_t<_Args>...> >(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...));}~thread() _NOEXCEPT{    // clean upif (joinable())_XSTD terminate();}thread(thread&& _Other) _NOEXCEPT: _Thr(_Other._Thr){  // move from _Other_Thr_set_null(_Other._Thr);}thread& operator=(thread&& _Other) _NOEXCEPT{   // move from _Otherreturn (_Move_thread(_Other));}thread(const thread&) = delete;thread& operator=(const thread&) = delete;void swap(thread& _Other) _NOEXCEPT{  // swap with _Other_STD swap(_Thr, _Other._Thr);}bool joinable() const _NOEXCEPT{   // return true if this thread can be joinedreturn (!_Thr_is_null(_Thr));}void join();void detach(){ // detach threadif (!joinable())_Throw_Cpp_error(_INVALID_ARGUMENT);_Thrd_detachX(_Thr);_Thr_set_null(_Thr);}id get_id() const _NOEXCEPT;static unsigned int hardware_concurrency() _NOEXCEPT{  // return number of hardware thread contextsstatic const unsigned int _Cnt = _Thrd_hardware_concurrency();return (_Cnt);}native_handle_type native_handle(){   // return Win32 HANDLE as void *return (_Thr._Hnd);}private:thread& _Move_thread(thread& _Other){   // move from _Otherif (joinable())_XSTD terminate();_Thr = _Other._Thr;_Thr_set_null(_Other._Thr);return (*this);}_Thrd_t _Thr;};

其中,thread类中的方法有:

void swap(thread& _Other);//bool joinable() ;//是否可以join调用void join(); //阻塞主线程等待子线程结束void detach();//分离子线程,主线程不被阻塞继续执行id get_id() ;//获取线程id

2.2 多线程简单的实例

创建线程的方法为std::thread th(函数名,参数1,参数2,...,参数n),若声明一个函数function(int a,int b),则创建线程的方法为std::thread th(fuction,a,b),

#include <iostream>
#include <thread>using namespace std;
void fun1(int num)
{cout<<"fun 1:"<<num<<endl;cout<<"fun1 线程id:"<<this_thread::get_id()<<endl;
}void fun2(int num)
{cout<<"fun 2:"<<num<<endl;cout<<"fun2 线程id:"<<this_thread::get_id()<<endl;
}int main()
{cout <<"主线程id:"<<this_thread::get_id()<< endl;std::thread a(fun1,1);std::thread b(fun2,2);a.join();b.join();return 0;
}

执行结果:


另外需要注意的是,当线程定义申明的时候就开始运行。join函数是等待子线程阻塞主线程,而detach函数是分离线程,主线程不会阻塞,继续执行。

2.3 互斥锁

C++ 11中头文件< mutex >中,定义了两个常用的类std::mutexstd::lock_guard,要使用互斥锁时,需要首先创建对象std::mutex mutex_;如果你用过Qt的互斥锁,会发现他们是一样的,Qt中使用QMutex来实现互斥锁。这里介绍的是C++ 11 实现互斥。

在这里,如果需要引用传递参数,需要使用std::ref()函数,如下面所示:

#include <iostream>
#include <thread>
#include <mutex>using namespace std;
mutex mutex_;void fun1(int &num)
{//mutex_.lock();num = num +1;cout<<"线程 fun 1:"<<num<<endl;//mutex_.unlock();
}void fun2(int &num)
{//mutex_.lock();num = num * 2;cout<<"线程 fun 2:"<<num<<endl;//mutex_.unlock();
}int main()
{int num=3;std::thread a(fun1,std::ref(num));std::thread b(fun2,std::ref(num));a.join();b.join();cout<<"主线程 num:"<<num<<endl;return 0;
}

输出结果:

但我们期待的输出结果是:

线程 fun 1:4
线程 fun 2:8
主线程 num:8

出现这种情况主要是两个子线程同时对互斥量操作,导致出现了错误;所以这时候就需要使用互斥锁来实现互斥。

2.3.1 使用std::mutex

#include <iostream>
#include <thread>
#include <mutex>using namespace std;
mutex mutex_;void fun1(int &num)
{mutex_.lock();num = num +1;cout<<"线程 fun 1:"<<num<<endl;mutex_.unlock();
}void fun2(int &num)
{mutex_.lock();num = num * 2;cout<<"线程 fun 2:"<<num<<endl;mutex_.unlock();
}int main()
{int num=3;std::thread a(fun1,std::ref(num));std::thread b(fun2,std::ref(num));a.join();b.join();cout<<"主线程 num:"<<num<<endl;return 0;
}

输出结果

2.3.2 使用std::lock_guard()

std::lock_guard的生命周期结束后,会自动执行析构函数,释放互斥量的控制权,相比于使用mutex::lockunlock而言,比较方便;但是没有后者灵活。另外,std::lock_guard可以使用{}来控制其生命周期。

#include <iostream>
#include <thread>
#include <mutex>using namespace std;
mutex mutex_;void fun1(int &num)
{// mutex_.lock();std::lock_guard<mutex> guard1(mutex_);num = num +1;cout<<"线程 fun 1:"<<num<<endl;//mutex_.unlock();
}void fun2(int &num)
{//mutex_.lock();std::lock_guard<mutex> guard2(mutex_);num = num * 2;cout<<"线程 fun 2:"<<num<<endl;//mutex_.unlock();
}int main()
{int num=3;std::thread a(fun1,std::ref(num));std::thread b(fun2,std::ref(num));a.join();b.join();cout<<"主线程 num:"<<num<<endl;return 0;
}

输出结果:

2.3.3 使用std::unique_lock()

虽然std::lock_guard提高了编程效率,但是使用std::lock_guard后不能手动lock()与手动unlock();如果你想在子线程的某个地方释放互斥量的控制权,std::lock_guard就显得很吃力,为此,C++提供了std::unique_lock,类似于std::lock_guard,保留了其功能,同时也可以手动解锁和上锁。
std::unique_lock支持传入参数defer_locktry_to_lockstd::adopt_lock,前者是默认不上锁,后者的用法和lock_guard类似,需要用guard.owns_lock()来判断是否锁住。

#include <iostream>
#include <thread>
#include <mutex>using namespace std;
mutex mutex_;void fun1(int &num)
{std::unique_lock<mutex> guard1(mutex_,std::defer_lock);guard1.lock();num = num +1;cout<<"线程 fun 1:"<<num<<endl;++num;guard1.unlock();
}void fun2(int &num)
{std::unique_lock<mutex> guard2(mutex_,std::defer_lock);guard2.lock();num = num * 2;cout<<"线程 fun 2:"<<num<<endl;num = num + 4;guard2.unlock();
}int main()
{int num=3;std::thread a(fun1,std::ref(num));std::thread b(fun2,std::ref(num));a.join();b.join();cout<<"主线程 num:"<<num<<endl;return 0;
}

执行结果:

2.4 原子操作

原子操作指不可分割的操作,只允许一个线程去操作原子变量。如果有很多个线程都要对一个变量进行操作,那么根据上面的内容,我们要不断的进行上锁和解锁,这样是非常麻烦的。原子类型的好处就体现出来了,定义一个原子变量,只允许单个线程来访问它,同时由于它的原子性,不需要上锁解锁,操作的效率和代码执行的效率更高。(包含在头文件< atomic >中)

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>using namespace std;
mutex mutex_;
std::atomic<bool> num(true);void fun1(void)
{num = false;
}void fun2(void)
{num = true;
}int main()
{std::thread a(fun1);std::thread b(fun2);a.join();b.join();cout<<"主线程 num:"<<num<<endl;return 0;
}

《C++(三)--多线程》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. 金蝶加密服务器显示339错误,打开金蝶软件出现这个提示怎么解决???
  2. windows如何访问linux系统文件,如何从 Windows 访问 Linux 文件?——方法步骤
  3. linux bash错误,linux bash错误重定向输出
  4. python输出小数_python输出小数
  5. *帅帅老师,编写函数,将999-9999整数放入一个数组当中
  6. 黑色脸谱_上演三幕的脸谱剧
  7. Google Chrome 插件推荐
  8. Oracle 各备份恢复环境下 SCN
  9. 程序员买啥游戏机,自己动手做一个体感小游戏
  10. 实验十八 CISCO设备IOS的备份与升级
  11. Abp mysql guid_使用ABP框架踩过的坑系列5
  12. 041孙悟空第三人称视角
  13. 三星SDS为韩国航运业成功测试区块链技术
  14. oracle10g无监听配置文件,Oracle 10g学习笔记(七) tnsnames.ora 监听配置文件详解
  15. 假币问题 (java)居然有假币!!!
  16. 怎样才算得上是一名优秀的软件测试工程师呢?
  17. Java我的2020年终盘点
  18. Spring容器 SpringMVC容器 web容器的关系
  19. C#delegate、event、Action、EventHandler 的使用和区别
  20. Trace32 SRST和TRST、system.attach 和 system.up的区别

热门文章

  1. hexo添加_hexo 如何给文章添加目录
  2. python 从入门到实践_Python编程从入门到实践日记Day36
  3. Linux环境安装PostgreSQL-12.2
  4. 不干胶设计用什么软件制作_用什么软件制作抖音短视频会比较好?
  5. 中国的网站能活几天?
  6. ActiveMQ(19):高级特性之独有消费者(Exclusive Consumer)
  7. PRD 如何编写好的需求文档
  8. ARM汇编程序设计之--数据搬移
  9. ORA-01033 ORACLE initialization or shutdow in progress
  10. 十进制到二进制的转换