1、多线程相关的类

C++11 新标准中引入了五个头文件来支持多线程编程,他们分别是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。

<atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
<thread>:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
<mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
<condition_variable>:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
<future>:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。

2、thread介绍

本节将详细介绍 std::thread 的用法。

std::thread 在 <thread> 头文件中声明,因此使用 std::thread 需包含 <thread> 头文件。

<thread> 头文件声明了 std::thread 线程类及 std::swap (交换两个线程对象)辅助函数。另外命名空间 std::this_thread 也声明在 <thread> 头文件中。下面是 C++11 标准所定义的 <thread> 头文件摘要:

namespace std
{#define __STDCPP_THREADS__ __cplusplusclass thread;void swap(thread& x, thread& y);namespace this_thread {thread::id get_id();void yield();template <class Clock, class Duration>void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);template <class Rep, class Period>void sleep_for(const chrono::duration<Rep, Period>& rel_time);}
}

<thread> 头文件主要声明了 std::thread 类,另外在 std::this_thread 命名空间中声明了 get_idyieldsleep_until 以及 sleep_for 等辅助函数,本节稍微会详细介绍 std::thread 类及相关函数。

std::thread 代表了一个线程对象,C++11 标准声明如下:

namespace std
{class thread {public:// 类型声明:class id;typedef implementation-defined native_handle_type;// 构造函数、拷贝构造函数和析构函数声明:thread() noexcept;template <class F, class ...Args>explicit thread(F&& f, Args&&... args);~thread();thread(const thread&) = delete;thread(thread&&) noexcept;thread& operator=(const thread&) = delete;thread& operator=(thread&&) noexcept;// 成员函数声明:void swap(thread&) noexcept;bool joinable() const noexcept;void join();void detach();id get_id() const noexcept;native_handle_type native_handle();// 静态成员函数声明:static unsigned hardware_concurrency() noexcept;};
}

std::thread 中主要声明三类函数:(1). 构造函数、拷贝构造函数及析构函数;(2). 成员函数;(3). 静态成员函数。另外, std::thread::id 表示线程 ID,同时 C++11 声明如下:

namespace std
{class thread::id {public:id() noexcept;};bool operator==(thread::id x, thread::id y) noexcept;bool operator!=(thread::id x, thread::id y) noexcept;bool operator<(thread::id x, thread::id y) noexcept;bool operator<=(thread::id x, thread::id y) noexcept;bool operator>(thread::id x, thread::id y) noexcept;bool operator>=(thread::id x, thread::id y) noexcept;template<class charT, class traits>basic_ostream<charT, traits>& operator<< (basic_ostream<charT, traits>& out, thread::id id);// Hash 支持template <class T> struct hash;template <> struct hash<thread::id>;
}

std::thread 构造函数

a. 默认构造函数,创建一个空的 thread 执行对象。
   b. 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
   c. 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
   d. move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detach.

std::thread 赋值操作

Move 赋值操作 (1) thread& operator=(thread&& rhs) noexcept;
拷贝赋值操作 [deleted] (2) thread& operator=(const thread&) = delete;

a.Move 赋值操作(1),如果当前对象不可 joinable,需要传递一个右值引用给 move 赋值操作;如果当前对象可被 joinable,则会调用 terminate() 报错。

b.拷贝赋值操作(2),被禁用,因此 std::thread 对象不可拷贝赋值。

join: join 线程,调用该函数会阻塞当前线程,直到由 *this 所标示的线程执行完毕 join 才返回。

到这里,就可以结合thread构造函数举个多线程的例子:

#include<iostream>
#include<thread>
#include<chrono>
using namespace std;void f1(int n)
{for (int i = 0; i < 5; ++i) {std::cout << "Thread " << n << " executing\n";std::this_thread::sleep_for(std::chrono::milliseconds(1000));//1秒}
}void f2(int& n)
{for (int i = 0; i < 5; ++i) {std::cout << "Thread 2 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(300));//0.3秒}
}int main()
{int n = 0;std::thread t1; // t1 is not a threadstd::thread t2(f1, n + 1); // pass by value,值传递std::thread t3(f2, std::ref(n)); // pass by reference,引用传递std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a threadt2.join();t4.join();std::cout << "Final value of n is " << n << '\n';system("pause");
}

为了显示出多线程的效果,我故意让f1延时一秒一循环,f2延时0.3秒一循环。上述的结果是不定的,f1和f2的输出可能交替进行(可能一个cout还没运行结束,但时间片结束,轮到另一个线程使用),以下是其中一种输出结果,你的结果可能与我不同:

detach: Detach 线程。 将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

调用 detach 函数之后:

  1. *this 不再代表任何的线程执行实例。
  2. joinable() == false
  3. get_id() == std::id()

另外,如果出错或者 joinable() == false,则会抛出 std::system_error

 #include <iostream>#include <chrono>#include <thread>void independentThread() {std::cout << "Starting concurrent thread.\n";std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Exiting concurrent thread.\n";}void threadCaller() {std::cout << "Starting thread caller.\n";std::thread t(independentThread);t.detach();std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Exiting thread caller.\n";}int main() {threadCaller();std::this_thread::sleep_for(std::chrono::seconds(5));}

joinable: 检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。另外,如果某个线程 已经执行完任务,但是没有被 join 的话,该线程依然会被认为是一个活动的执行线程,因此也是可以被 join 的。

  #include <iostream>#include <thread>#include <chrono>using namespace std;void foo(){std::this_thread::sleep_for(std::chrono::seconds(1));}int main(){std::thread t;cout << "before starting, joinable: " << t.joinable() << endl; //值为0t = std::thread(foo);cout << "after starting, joinable: " << t.joinable() << endl; //值为1t.join();cout << "after join(), joinable: " << t.joinable() << endl; //值为0}

get_id: 获取线程 ID,返回一个类型为 std::thread::id 的对象。请看下面例子:

  #include <iostream>#include <thread>#include <chrono>void foo(){std::this_thread::sleep_for(std::chrono::seconds(1));}int main(){std::thread t1(foo);std::thread::id t1_id = t1.get_id();std::thread t2(foo);std::thread::id t2_id = t2.get_id();std::cout << "t1's id: " << t1_id << '\n';std::cout << "t2's id: " << t2_id << '\n';t1.join();t2.join();}

主线程分出的子线程,子线程我电脑得到的get_id值为76648,等线程结束(join之后)该值回到0;如果没有线程(比如使用默认构造函数),则该值一直为0。

swap: Swap 线程,交换两个线程对象所代表的底层句柄(underlying handles)。

 #include <iostream>#include <thread>#include <chrono>void foo(){std::this_thread::sleep_for(std::chrono::seconds(1));}void bar(){std::this_thread::sleep_for(std::chrono::seconds(1));}int main(){std::thread t1(foo);std::thread t2(bar);std::cout << "thread 1 id: " << t1.get_id() << std::endl;std::cout << "thread 2 id: " << t2.get_id() << std::endl;std::swap(t1, t2);std::cout << "after std::swap(t1, t2):" << std::endl;std::cout << "thread 1 id: " << t1.get_id() << std::endl;std::cout << "thread 2 id: " << t2.get_id() << std::endl;t1.swap(t2);std::cout << "after t1.swap(t2):" << std::endl;std::cout << "thread 1 id: " << t1.get_id() << std::endl;std::cout << "thread 2 id: " << t2.get_id() << std::endl;t1.join();t2.join();}

执行结果如下:

thread 1 id: 1892
thread 2 id: 2584
after std::swap(t1, t2):
thread 1 id: 2584
thread 2 id: 1892
after t1.swap(t2):
thread 1 id: 1892
thread 2 id: 2584

yield: 当前线程放弃执行,操作系统调度另一线程继续执行。

  #include <iostream>#include <chrono>#include <thread>// "busy sleep" while suggesting that other threads run // for a small amount of timevoid little_sleep(std::chrono::microseconds us){auto start = std::chrono::high_resolution_clock::now();auto end = start + us;do{std::this_thread::yield();} while (std::chrono::high_resolution_clock::now() < end);}int main(){auto start = std::chrono::high_resolution_clock::now();little_sleep(std::chrono::microseconds(100));auto elapsed = std::chrono::high_resolution_clock::now() - start;std::cout << "waited for "<< std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()<< " microseconds\n";}

sleep_until: 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

 template< class Clock, class Duration >void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );

sleep_for: 线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长。

 template< class Rep, class Period >void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration );#include <iostream>#include <chrono>#include <thread>int main(){std::cout << "Hello waiter" << std::endl;std::chrono::milliseconds dura( 2000 );std::this_thread::sleep_for( dura );std::cout << "Waited 2000 ms\n";}

执行结果如下:

Hello waiter
Waited 2000 ms

native_handle: 返回 native handle(由于 std::thread 的实现和操作系统相关,因此该函数返回与 std::thread 具体实现相关的线程句柄,例如在符合 Posix 标准的平台下(如 Unix/Linux)是 Pthread 库)。

  #include <thread>#include <iostream>#include <chrono>#include <cstring>#include <pthread.h>std::mutex iomutex;void f(int num){std::this_thread::sleep_for(std::chrono::seconds(1));sched_param sch;int policy; pthread_getschedparam(pthread_self(), &policy, &sch);std::lock_guard<std::mutex> lk(iomutex);std::cout << "Thread " << num << " is executing at priority "<< sch.sched_priority << '\n';}int main(){std::thread t1(f, 1), t2(f, 2);sched_param sch;int policy; pthread_getschedparam(t1.native_handle(), &policy, &sch);sch.sched_priority = 20;if(pthread_setschedparam(t1.native_handle(), SCHED_FIFO, &sch)){std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n';}t1.join();t2.join();}

执行结果如下:

Thread 2 is executing at priority 0
Thread 1 is executing at priority 20

hardware_concurrency [static]: 检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint)。

  #include <iostream>#include <thread>int main() {unsigned int n = std::thread::hardware_concurrency();std::cout << n << " concurrent threads are supported.\n";}

3、经典举例

(1)多线程访问,进一步了解join的作用:计算num的值,期望得到2 0000 0000

#include<iostream>
#include<thread>
#include<mutex>const int N = 100000000;
int num(0);
mutex m;void run()
{for (int i = 0; i < N; i++){//m.lock();  //当前线程加锁后,未释放前,其它线程不可访问下面语句num++;//m.unlock();}
}int main()
{clock_t start = clock();thread t1(run);thread t2(run);t1.join();t2.join();clock_t end = clock();cout << "num=" << num << ",用时 " << end - start << " ms" << endl;system("pause");return 0;
}

上述例子中,本机器VS2013跑出来结果如下(如你所见,该结果不定):

显然与预期结果不对。问题出在哪?  是的,多线程是异步访问。当多线程访问同一个资源,可能某一刻,两个线程同时对num进行+1操作。比如一个线程 t1 当前的num值为100,在执行++的时候,时间片结束,轮到另一个线程 t2 执行,运算num++,得到num==101;然后时间片轮回到线程 t1 执行n++,此时num++的值是从100开始加的。因为右侧当时num值为100,属于临时变量。num++换算成原子操作,类似这样的表达形式:

int temp = num;//语句1

num = temp + 1;//语句2

所以上述对于“同时访问”可能按照下列方式执行:

(1)时间片1:t1执行语句1,此时temp取num的值为100

(2)时间片2:t2执行语句1,temp值也是100

(3)时间片3:t2执行语句2,num值为101。该循环结束,临时变量temp释放

(4)时间片4:t1执行语句2,但是此时保存为时间片1中临时变量temp,值为100,运算结果num也是101

所以上述会出现最终结果小于预计值的情况。

知道了原因,看一下怎么解决呢?

方法一:加互斥锁

如上代码中放开注释的mutex锁

得到结果如下:

结果对了,但因为加解锁都需要时间,所以效率不高。

方法二:让线程分开执行

#include<iostream>
#include<thread>
#include<mutex>const int N = 100000000;
int num(0);
mutex m;void run()
{for (int i = 0; i < N; i++){num++;}
}int main()
{clock_t start = clock();thread t1(run);t1.join();thread t2(run);t2.join();clock_t end = clock();cout << "num=" << num << ",用时 " << end - start << " ms" << endl;system("pause");return 0;
}

先看一下结果:

当线程t1执行run()的时候,使用join会阻塞当前主线程。也就是说当执行join之后,当前所有线程不受影响继续执行(当前已经启动的线程继续),而join之后的语句会被阻塞,直到当前添加join的线程运行结束才会重新继续后面的语句。

C++多线程thread用法相关推荐

  1. Python爬虫进阶五之多线程的用法

    前言 我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理. 首先声明一点! 多线程和多进程是不一样的!一个是 thread ...

  2. python queue threading_Python 多线程 -thread threading Queue- 简单学习

    Python 多线程 -thread threading Queue- 简单学习 在实际工作过程中,会出现需要并发的做一些事情,例如一台机器测到几千台机器的网络连通性,如果你单线程一台一台测的话,会花 ...

  3. Python 爬虫进阶五之多线程的用法

    我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理. 首先声明一点! 多线程和多进程是不一样的!一个是 thread 库,一 ...

  4. 多线程Thread类创建多线程

    package com.ajax; //多线程Thread类创建多线程 public class Example02 {public static void main(String[] args){n ...

  5. python的多线程threading_Python中多线程thread与threading的实现方法,pythonthreading

    Python中多线程thread与threading的实现方法,pythonthreading 学过Python的人应该都知道,Python是支持多线程的,并且是native的线程.本文主要是通过th ...

  6. 6-7 jmu-Java-07多线程-Thread (3分)

    6-7 jmu-Java-07多线程-Thread (3分) 编写MyThread类继承自Thread.创建MyThread类对象时可指定循环次数n. 功能:输出从0到n-1的整数. 并在最后使用Sy ...

  7. Python中多线程thread与threading的实现方法

    Python中多线程thread与threading的实现方法 这篇文章主要介绍了Python中多线程thread与threading的实现方法,很重要的应用,需要的朋友可以参考下 学过Python的 ...

  8. Java多线程-Thread、Runnable、Executor

    线程分为五个阶段:创建.就绪.运行.阻塞.终止. 创建:新建一个线程对象,如Thread thd=new Thread(). 就绪:创建了线程对象后,调用了线程的start()方法(此时线程只是进入了 ...

  9. 多线程——Thread类的基本用法

    一.线程创建 创建线程最常用的方法是使用lambda表达式 public class Thread {public static void main(String[] args) {Thread t ...

最新文章

  1. 自然语言处理(NLP)前沿进展报告
  2. linux下vi命令修改文件及保存的使用方法
  3. Makefile文件和shell脚本
  4. 【APP Web架构】企业web高可用集群实战之haproxy篇续(二)
  5. 进口网友讨论:是什么让你继续支持并持有BCH?
  6. JavaScript之使用AJAX(适合初学者)
  7. Windows 8的无线设置后,竟不能直接更改,目前知道可以通过命令行解决
  8. boost::geometry::strategy::distance::haversine用法的测试程序
  9. 9.OD-断点、命令
  10. Android low memory killer 机制
  11. SciPy和Numpy处理能力
  12. jquery如何阻止子元素继承父元素的事件(又称事件冒泡)
  13. 【自用】手工编译lnmp环境
  14. 0-简单工厂模式类图
  15. Google 按图搜索的原理
  16. 二进制安装MySQL以及密码破解
  17. TEM Imaging Analysis (TIA) for win10
  18. 怎样读懂计算机论文算法,最新计算机算法分析论文参考文献 计算机算法分析专著类参考文献有哪些...
  19. Java猿社区—Apache Commons Collections—CollectionUtils工具类详解
  20. linux vim配置视频教程,Vim实战视频教程

热门文章

  1. BBC纪录片《化学史》,看看历史上化学家们的脑洞有多大?
  2. 【C/C++】译密码问题以及ASCII码表的总结
  3. 高德地图自定义创建地图
  4. 未来的互联网创业者,你们做好准备了么?
  5. 基于MirrorMaker与火山引擎的Kafka数据同步
  6. 100款表白源码,搭建网站必备,总有一款适合你
  7. 09#R语言实现决策树分析
  8. 如何在米拓的metinfo的模板中加入自己定义的样式
  9. zabbix mysql qps_zabbix监控mysql的tps和qps
  10. fsync mysql_使用O_DIRECT_NO_FSYNC来提升MySQL性能