C++11提供的线程库的好处:可以跨平台 + 提供了原子类型的变量
C++11中最重要的特性就是对线程进行了支持,是的C++在并行编程时不需要依赖第三方库,而且 在原子操作中还引入了原子类的概念,要使用标准库中的线程,必须包含< thread >头文件。
注意:
1.线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
2.当创建一个宣传对象后,没有提供线程函数,该对象实际没有对应任何线程
3.当创建一个线程后,并且给线程关联线程函数,该线程函数就被启动,与主线程一起运行。线程函数一般情况下可以按照一下三种方式提供:
函数指针,lambda表达式,函数对象(仿函数)
C++线程库通过构造一个线程对象来启动一个线程,该线程对象中就包含了线程运行时的上下文环境,比如:线程函数、线程栈、线程起始状态等以及线程ID等,所有操作全部封装在一起,最后在底层统一传递给_beginthreadex() 创建线程函数来实现 (注意:_beginthreadex是windows中创建线程的底层c函数)
// 使用lambda表达式作为线程函数创建线程
int main()
{
int n1 = 500;
int n2 = 600;
thread t([&](int addNum){
n1 += addNum;
n2 += addNum;
}, 500);
t.join();
std::cout << n1 << ' ' << n2 << std::endl;
return 0;
}
//使用函数指针的方式来进行创建线程
void func(int val)
{
cout<<val<<endl;
}
int main()
{
//使用函数指针来创建线程
thread t1(func,100);
t1.join();
return 0;
}
//使用仿函数,函数对象来创建线程
class TF
{
public:
void func(int val)
{
cout<<val<<endl;
}
};
int main()
{
thread t2(TF(),(200));
t2.join();
return 0;
}
线程执行过程:
线程函数参数:
注意:
启动了一个线程后,当这个线程结束的时候,如何去回收线程所使用的资源呢?thread库给我们两种选择:
加入式:join()
join():会主动地等待线程的终止。在调用进程中join(),当新的线程终止时,join()会清理相关的资源,然后返回,调用线程再继续向下执行。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程的对象每次你只能使用一次join(),当你调用的join()之后joinable()就将返回false了。
分离式:detach()
detach:会从调用线程中分理出新的线程,之后不能再与新线程交互。就是你和你女朋友分手,那之后你们就不会再有联系(交互)了,而她的之后消费的各种资源也就不需要你去埋单了(清理资源)。此时调用joinable()必然是返回false。分离的线程会在后台运行,其所有权和控制权将会交给
c++运行库。同时,C++运行库保证,当线程退出时,其相关资源的能够正确的回收。
注意:必须在thread对象销毁之前做出选择,这是因为线程可能在你加入或分离线程之前,就已经结束了,之后如果再去分离它,线程可能会在thread对象销毁之后继续运行下去。
原子性操作库:
C++98中传统的解决方式:可以对共享修改的数据可以加锁保护。
#include <iostream>
using namespace std;
#include <thread>
#include <mutex>
std::mutex m;
unsigned long sum = 0L;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
{
m.lock();
sum++;
m.unlock();
}
}
int main()
{
cout << "Before joining,sum = " << sum << std::endl;
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << "After joining,sum = " << sum << std::endl;
return 0;
}
C++11使用原子类变量。
#include <iostream>
using namespace std;
#include <thread>
#include <atomic>
atomic_long sum{ 0 };
void fun(size_t num) {
for (size_t i = 0; i < num; ++i)
sum ++; // 原子操作
}
int main()
{
cout << "Before joining, sum = " << sum << std::endl;
thread t1(fun, 1000000);
thread t2(fun, 1000000);
t1.join();
t2.join();
cout << "After joining, sum = " << sum << std::endl;
return 0; }
虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。因此C++11中引入了原子操作。