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__ __cplusplus
class 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_id
,yield
,sleep_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 或者将其设置为 detached.
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 thread
std::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 thread
t2.join();
t4.join();
std::cout << "Final value of n is " << n << '\n';
system("pause");
}
为了显示出多线程的效果,我故意让f1延时一秒一循环,f2延时0.3秒一循环。上述的结果是不定的,f1和f2的输出可能交替进行(可能一个cout还没运行结束,但时间片结束,轮到另一个线程使用),以下是其中一种输出结果,你的结果可能与我不同:
detach
: Detach 线程。 将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。
调用 detach 函数之后:
*this
不再代表任何的线程执行实例。- joinable() == false
- 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; //值为0
t = std::thread(foo);
cout << "after starting, joinable: " << t.joinable() << endl; //值为1
t.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 time
void 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,在执行num++的时候,时间片结束,轮到另一个线程 t2 执行,运算num++,得到num==101,然后轮回另一个线程 t1 执行num++,此时num++(可以简单理解为num= num +1)的值是从100开始加的。因为右侧当时num值为100,属于临时变量,可以起个别名。num++换算成原子操作,类似这样的表达形式:
int temp = num;//语句1
num = temp + 1;//语句2
所以上述对于“同时访问”可以理解为:当t1执行完语句1的时候,temp值为100,此时t2执行num++,虽然得到num==101,但是此时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的线程运行结束才会重新继续后面的语句。