与 C++11 多线程相关的头文件地方
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() 函数就声明在此头文件中。
#include <iostream>
#include <thread>
using namespace std;
void th_function()
{
std::cout << "hello thread." << std::endl;
}
int main(int argc, char *argv[])
{
std::thread t(th_function);
t.join();
return 0;
}
std::thread 构造
(1). 默认构造函数,创建一个空的 thread 执行对象。
(2). 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
(3). 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
(4). move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.
看一个例子:
这个例子中创建了两个线程,其中一个线程函数带参数,大家仔细分析上面的thread类的构函数,看如何创建线程。
// thread example
#include <iostream> // std::cout
#include <thread> // std::thread
void thr_function1()
{
for (int i = 0; i != 10; ++i)
{
std::cout << "thread 1 print " << i << std::endl;
}
}
void thr_function2(int n)
{
std::cout << "thread 1 print " << n << std::endl;
}
int main()
{
std::thread t1(thr_function1); // spawn new thread that calls foo()
std::thread t2(thr_function2, 111); // spawn new thread that calls bar(0)
std::cout << "main, foo and bar now execute concurrently...\n";
// synchronize threads:
t1.join(); // pauses until first finishes
t2.join(); // pauses until second finishes
std::cout << "thread 1 and htread 2 completed.\n";
return 0;
}
大家的输出可能不是这样,有可能是下面这样的,不要紧,因为这个例子中没使用多线程的异步机制,线程之间存在竞争,看稍后文章讲解所以输出可能是下面这样的情况。
三、move 赋值操作
move (1)
thread& operator= (thread&& rhs) noexcept;
copy [deleted] (2)
thread& operator= (const thread&) = delete;
(1). move 赋值操作,如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则 terminate() 报错。
(2). 拷贝赋值操作被禁用,thread 对象不可被拷贝。
看下面的例子。
// example for thread::operator=
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::sleep_for
#include <chrono> // std::chrono::seconds
void pause_thread(int n)
{
std::this_thread::sleep_for(std::chrono::seconds(n));//c++11 this_thread 类
std::cout << "pause of " << n << " seconds ended\n";
}
int main()
{
std::thread threads[5]; // default-constructed threads
std::cout << "Spawning 5 threads...\n";
for (int i = 0; i<5; ++i)
threads[i] = std::thread(pause_thread, i + 1); // move-assign threads 这里调用move复制函数
std::cout << "Done spawning threads. Now waiting for them to join:\n";
for (int i = 0; i<5; ++i)
threads[i].join();
std::cout << "All threads joined!\n";
return 0;
}
1、成员类型和成员函数。
2、std::thread 构造函数。
3、异步。
4、多线程传递参数。
5、join、detach。
6、获取CPU核心个数。
7、CPP原子变量与线程安全。
8、lambda与多线程。
9、时间等待相关问题。
10、线程功能拓展。
11、多线程可变参数。
12、线程交换。
13、线程移动。
1、成员类型和成员函数。
2、std::thread 构造函数。
(1).默认构造函数,创建一个空的 thread 执行对象。
(2).初始化构造函数,创建一个 thread 对象,该 thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
(3).拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
(4).move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached。
#include<thread>
#include<chrono>
using namespace std;
void fun1(int n) //初始化构造函数
{
cout << "Thread " << n << " executing\n";
n += 10;
this_thread::sleep_for(chrono::milliseconds(10));
}
void fun2(int & n) //拷贝构造函数
{
cout << "Thread " << n << " executing\n";
n += 20;
this_thread::sleep_for(chrono::milliseconds(10));
}
int main()
{
int n = 0;
thread t1; //t1不是一个thread
thread t2(fun1, n + 1); //按照值传递
t2.join();
cout << "n=" << n << '\n';
n = 10;
thread t3(fun2, ref(n)); //引用
thread t4(move(t3)); //t4执行t3,t3不是thread
t4.join();
cout << "n=" << n << '\n';
return 0;
}
运行结果:
Thread 1 executing
n=0
Thread 10 executing
n=30
3、异步
#include<thread>
using namespace std;
void show()
{
cout << "hello cplusplus!" << endl;
}
int main()
{
//栈上
thread t1(show); //根据函数初始化执行
thread t2(show);
thread t3(show);
//线程数组
thread th[3]{thread(show), thread(show), thread(show)};
//堆上
thread *pt1(new thread(show));
thread *pt2(new thread(show));
thread *pt3(new thread(show));
//线程指针数组
thread *pth(new thread[3]{thread(show), thread(show), thread(show)});
return 0;
}
4、多线程传递参数
#include<thread>
using namespace std;
void show(const char *str, const int id)
{
cout << "线程 " << id + 1 << " :" << str << endl;
}
int main()
{
thread t1(show, "hello cplusplus!", 0);
thread t2(show, "你好,C++!", 1);
thread t3(show, "hello!", 2);
return 0;
}
运行结果:
线程 1线程 2 :你好,C++!线程 3 :hello!
:hello cplusplus!
5、join、detach
#include<thread>
#include<array>
using namespace std;
void show()
{
cout << "hello cplusplus!" << endl;
}
int main()
{
array<thread, 3> threads = { thread(show), thread(show), thread(show) };
for (int i = 0; i < 3; i++)
{
cout << threads[i].joinable() << endl;//判断线程是否可以join
threads[i].join();//主线程等待当前线程执行完成再退出
}
return 0;
}
运行结果:
hello cplusplus!
hello cplusplus!
1
hello cplusplus!
1
1
总结:
join 是让当前主线程等待所有的子线程执行完,才能退出。
detach例子如下:
#include<thread>
using namespace std;
void show()
{
cout << "hello cplusplus!" << endl;
}
int main()
{
thread th(show);
//th.join();
th.detach();//脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。
//detach以后,子线程会成为孤儿线程,线程之间将无法通信。
cout << th.joinable() << endl;
return 0;
}
运行结果:
hello cplusplus!
结论:
线程 detach 脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。
线程 detach以后,子线程会成为孤儿线程,线程之间将无法通信。
6、获取CPU核心个数。
例如:
#include<thread>
using namespace std;
int main()
{
auto n = thread::hardware_concurrency();//获取cpu核心个数
cout << n << endl;
return 0;
}
运行结果:
8
结论:
通过 thread::hardware_concurrency() 获取 CPU 核心的个数。
7、CPP原子变量与线程安全
问题例如:
**#include<thread>
using namespace std;
const int N = 100000000;
int num = 0;
void run()
{
for (int i = 0; i < N; i++)
{
num++;
}
}
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;
return 0;
}
运行结果:
num=143653419,用时 730 ms
从上述代码执行的结果,发现结果并不是我们预计的200000000,这是由于线程之间发生冲突,从而导致结果不正确。
为了解决此问题,有以下方法:
(1)互斥量。
例如:
#include<thread>
#include<mutex>
using namespace std;
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;
return 0;
}
运行结果:
num=200000000,用时 128323 ms
不难发现,通过互斥量后运算结果正确,但是计算速度很慢,原因主要是互斥量加解锁需要时间。
互斥量详细内容 请参考下篇文章C++11 并发之std::mutex。
(2)原子变量。
例如:
#include<thread>
#include<atomic>
using namespace std;
const int N = 100000000;
atomic_int num{ 0 };//不会发生线程冲突,线程安全
void run()
{
for (int i = 0; i < N; i++)
{
num++;
}
}
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;
return 0;
}
运行结果:
num=200000000,用时 29732 ms
不难发现,通过原子变量后运算结果正确,计算速度一般。
原子变量详细内容 请参考C++11 并发之std::atomic。
(3)加入 join 。
例如:
#include<thread>
using namespace std;
const int N = 100000000;
int num = 0;
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;
return 0;
}
运行结果:
num=200000000,用时 626 ms
不难发现,通过原子变量后运算结果正确,计算速度也很理想。
8、lambda与多线程。
例如:
#include<thread>
using namespace std;
int main()
{
auto fun = [](const char *str) {cout << str << endl; };
thread t1(fun, "hello world!");
thread t2(fun, "hello beijing!");
return 0;
}
运行结果:
hello world!
hello beijing
9、时间等待相关问题。
例如:
#include<thread>
#include<chrono>
using namespace std;
int main()
{
thread th1([]()
{
//让线程等待3秒
this_thread::sleep_for(chrono::seconds(3));
//让cpu执行其他空闲的线程
this_thread::yield();
//线程id
cout << this_thread::get_id() << endl;
});
return 0;
}
10、线程功能拓展。
例如:
#include<thread>
using namespace std;
class MyThread :public thread //继承thread
{
public:
//子类MyThread()继承thread()的构造函数
MyThread() : thread()
{
}
//MyThread()初始化构造函数
template<typename T, typename...Args>
MyThread(T&&func, Args&&...args) : thread(forward<T>(func), forward<Args>(args)...)
{
}
void showcmd(const char *str) //运行system
{
system(str);
}
};
int main()
{
MyThread th1([]()
{
cout << "hello" << endl;
});
th1.showcmd("calc"); //运行calc
//lambda
MyThread th2([](const char * str)
{
cout << "hello" << str << endl;
}, " this is MyThread");
th2.showcmd("notepad");//运行notepad
return 0;
}
运行结果:
hello
//运行calc
hello this is MyThread
//运行notepad
11、多线程可变参数
例如:
#include<thread>
#include<cstdarg>
using namespace std;
int show(const char *fun, ...)
{
va_list ap;//指针
va_start(ap, fun);//开始
vprintf(fun, ap);//调用
va_end(ap);
return 0;
}
int main()
{
thread t1(show, "%s %d %c %f", "hello world!", 100, 'A', 3.14159);
return 0;
}
运行结果:
hello world! 100 A 3.14159
12、线程交换。
例如:
#include<thread>
using namespace std;
int main()
{
thread t1([]()
{
cout << "thread1" << endl;
});
thread t2([]()
{
cout << "thread2" << endl;
});
cout << "thread1' id is " << t1.get_id() << endl;
cout << "thread2' id is " << t2.get_id() << endl;
cout << "swap after:" << endl;
swap(t1, t2);//线程交换
cout << "thread1' id is " << t1.get_id() << endl;
cout << "thread2' id is " << t2.get_id() << endl;
return 0;
}
运行结果:
thread1
thread2
thread1' id is 4836
thread2' id is 4724
swap after:
thread1' id is 4724
thread2' id is 4836
两个线程通过 swap 进行交换。
13、线程移动
例如:
#include<thread>
using namespace std;
int main()
{
thread t1([]()
{
cout << "thread1" << endl;
});
cout << "thread1' id is " << t1.get_id() << endl;
thread t2 = move(t1);;
cout << "thread2' id is " << t2.get_id() << endl;
return 0;
}
运行结果:
thread1
thread1' id is 5620
thread2' id is 5620
从上述代码中,线程t2可以通过 move 移动 t1 来获取 t1 的全部属性,而 t1 却销毁了。