c++11多线程库:std::thread
一、join函数:用于等待线程对象运行结束
程序从main函数开始,本来由一个线程执行;当执行到std::thread定义一个线程对象,给定初始构造函数后,就创建了一个新的子线程,并进行子线程独立计算;此时线程数量一分为二。
#include <iostream>
#include <thread>
#include <random>
void test_thread(){
std::cout<<"hello world"<<std::endl;
}
int main() {//初始main函数,主线程
std::thread t(test_thread);//创建了另一个线程
t.join();//主线程等待子线程结束,才能执行下一条语句
return 0;
}
二、detach函数:子线程从主线程中分离
这种情况下,就相当于子线程已经自力更生,可能主线程已经挂了,子线程也可以继续生存,这种就类似于后台运行线程。
#include <iostream>
#include <thread>
#include <math.h>
void test_thread(){
for (int i = 0; i < 1000; ++i) {
float si=sqrt(i);
std::cout<<"thread:"<<i<<"\t"<<si<<std::endl;
}
}
int main() {//初始main函数,主线程
std::thread t(test_thread);//创建了另一个线程
t.detach();//分离子线程,子线程于主线程不相关,无需等待,这种情况下,整个main函数结束运行,可能子线程还在运行
for (int i = 0; i < 100; ++i) {
float si=sqrt(i);
std::cout<<"main:"<<i<<"\t"<<si<<std::endl;
}
return 0;
}
这个程序可以看到,两个线程交互打印出相关信息,但是一旦主线程main循环到了100次,程序运行结束了,子线程还在运行,不过控制台main主线程已经不能输出。
三、多个子线程
#include <iostream>
#include <thread>
#include <random>
void test_thread(int i){
float sum=1;
for(int j=0;j<1000;j++){
sum+=j*0.978;
}
std::chrono::milliseconds dura(i*100);
std::this_thread::sleep_for(dura);
std::cout<<"thread:"<<i<<"output:"<<sum<<"\n"<<std::endl;
}
int main() {
std::thread threads[5];//创建5个线程对象
for (int i = 0; i < 5; i++) {
threads[i] = std::thread(test_thread, i);//启动各个线程,并计算
}
for (auto& t: threads) {
t.join();//等待所有的线程结束
}
std::cout << "end all thread"<<std::endl;
return 0;
}
四、线程对象的移动、线程ID
#include <iostream> #include <thread> #include <math.h> #include <sstream> void test_thread(){ auto myid = std::this_thread::get_id();//获取线程ID, std::stringstream ss; ss << myid; std::cout<<ss.str()<<std::endl; } int main() {//初始main函数,主线程 test_thread();//线程1 std::thread t1(test_thread);//创建了另一个线程 std::thread t2(std::move(t1));//线程移动,类似于线程剪切,可以看到打印出来的是线程2 t2.join(); return 0; }线程对象被剪切后,此时t1将不代表任何线程,后续不要继续调用t1.join()
五、互斥量
以前在用openmp的时候,经常出现的名词:互斥锁。互斥量说白了,就是为了访问共享变量读取,需要开启的锁,以防止多个线程同时对某个共享变量进行修改操作,而引起混乱。这个在编写线程池的时候,尤其重要,任务队列就是共享变量,不允许多个线程共同进行入队、出队操作。
看一下下面这个例子,如果对共享变量count的累加,不使用互斥锁的话,会引起错乱,最后count的值不确定,应该是位于10w到20w之间的一个数:
#include <iostream> #include <thread> #include <math.h> #include <sstream> #include <mutex> std::mutex lock; int count=0; void test_thread(){ // lock.lock(); for(int i=0;i<100000;i++){ count++; } // lock.unlock(); } int main() {//初始main函数,主线程 std::thread t1(test_thread);//创建了另一个线程 std::thread t2(test_thread);//线程移动,类似于线程剪切,可以看到打印出来的是线程2 t1.join(); t2.join(); std::cout<<count<<std::endl; return 0; }主要原因就是两个线程同时访问、修改共享变量,而引起的。因此需要采用互斥量,在某个线程访问修改的时候,另外一个线程不允许修改访问,被阻塞在lock()语句,直到获得互斥量所有权为止。如果有其它线程在访问共享变量,那么你就需要等一等。
#include <iostream> #include <thread> #include <math.h> #include <sstream> #include <mutex> std::mutex lock; int count=0; void test_thread(){ lock.lock(); for(int i=0;i<100000;i++){ count++; } lock.unlock(); } int main() {//初始main函数,主线程 std::thread t1(test_thread);//创建了另一个线程 std::thread t2(test_thread);//线程移动,类似于线程剪切,可以看到打印出来的是线程2 t1.join(); t2.join(); std::cout<<count<<std::endl; return 0; }六、条件变量condition_variable
条件变量是另一种同于等待通知的同步机制,当某个线程运行到语句:condition_variable变量调用成员函数wait语句的时候:如果wait函数的第二个参数是true,线程继续运行;如果wait函数的第二个参数为false,线程将在该语句进行等待,等到condition_variable对象调用notify_one、notify_all的时候,此时会通知wait重新进行判断第二个参数的真假情况,判断时候继续等待。
condition_variable变量,一般配合std::unique_lock<std::mutex>进行等待:
#include <iostream> #include <thread> #include <math.h> #include <sstream> #include <mutex> #include <condition_variable> #include <queue> std::mutex mtx; std::condition_variable cv; std::queue<std::string>test_queue; void test_thread(){ std::unique_lock<std::mutex>ulock(mtx); cv.wait(ulock,[]{ return test_queue.size()>=10;});//判断队列是否满足某个条件,如果满足了,那么继续往下执行 test_queue.push("t1 thread"); } int main() {//初始main函数,主线程 std::thread t1(test_thread);//创建了另一个线程 for(int i=1;i<15;i++){ cv.notify_all();//唤醒所有线程,此时会去t1线程中,判断条件变量cv.wait阻塞条件,如果为true,那么就t1就无需继续等待,往下执行 std::chrono::milliseconds time(1000); std::this_thread::sleep_for(time); test_queue.push("main thread"); } while (!test_queue.empty()){ std::cout<<test_queue.front()<<std::endl; test_queue.pop(); } t1.join(); return 0; }上面例子,在编写线程池中,尤为重要,因为我们可以用于判断任务队列是否满了、是否为空,然后线程才能执行。
七、原子变量
上面在遇到共享变量的时候,需要使用mutex加锁,防止多个线程同时修改共享变量,挺麻烦的。c++11多线程还有个牛逼的类型,称之为原子类型:std::atomic,这种类型的共享变量,可以自动实现互斥功能,不需要互斥量来声明保护它。
fdaf