C++11 ,封装了thread的多线程的类,这样对多线程的使用更加方便。
多线程的原理我不加赘述,可以参看操作系统等参考书。
多线程代码可以最大化利用计算机性能资源,提高代码的运行效率,是常用优化方法。
我不是C++大神,初学阶段的菜鸟而已,很多问题我还是不理解当中的原理,写这篇博客的原因,也是记录自己的学习心得和思路,供自己日后自己思考。
首先从简单的问题入手,如何写一个多线程的C++代码?
#include<iostream> #include<thread> void fun(int a){ a++; } int main(){ int a=0; std::thread t(fun,a); //创建一个线程t,t调用函数fun,a作为fun的参数,也要写到thread的构造函数当中;
t.join(); //启动线程t,并且阻塞主线程,等到线程t运行结束后,再继续运行主线程; std::cout<<a<<std::endl; }
上面这段代码是最简单的多线程代码,调用thread类,并利用了thread的构造函数创建一个线程t,thread类的构造函数重载了很多,后面会继续说到。
在这里要说一下,thread类当中的两个成员函数,join()和detach()。这两个成员的作用就像上面代码的注释那样,启动新生成的线程的,但是区别在于join()函数是启动子线程而阻塞主线程,当子线程运行结束后,才会继续运行主线程。相比之下,detach()函数的作用是启动子线程,并且让子线程和主线程分离,子线程和主线程各运行各的,虽然两个线程会因为共享内存池的原因在操作系统的层面发生发生阻塞等关系,但是在代码层次上,两个线程并不存在谁阻塞谁,很可能主线程已经运行结束了,子线程还在运行。
接下来,我们要说一下类当中的成员函数如何初始化thread类的构造函数。
对于类的成员函数,我们需要给出类对象的地址:
#include<iostream> #include<thread> class A{ public: void fun(int a,int b){ std::cout<<"this is A thread!"<<a<<std::endl; } }; int main(){ int k=0; A a; std::thread t(&A::fun,a,k,k+1); t.join(); }
std::thread t(&A::fun,a,k,k+1); 这个地方就可以看出thread类的构造对于成员函数的重载了,std::thread t(函数(成员函数)地址,对象地址,成员函数的参数1,参数2,参数3...)。
相比非成员函数,成员函数需要给出类实例化对象的地址,如果该线程是在同一类的某一成员函数当中被构造,则直接用this关键字代替即可。
其实,我在写成员函数的多线程代码的时候,发现成员函数的需要传递的参数太多会使thread类的构造函数重载失败,我测试了一下,成员函数最多只能传递4个参数,也就说std::thread类的构造函数最多只能重载6个参数。
这一点,我并没有找到相关文档得到证实,只是在写代码的时候发现成员函数传递参数太多,会一直编译不通过,偶然间发现这个点的,具体到底对不对,我也不是很确定。
其次,我们要说一下加锁和解锁的问题。
因为我们创造的每一个线程只要在一个进程内,都是共享内存池的,这样在读写数据可能会发生混乱。
C++11提供了mutex类进行加锁和解锁。
#include<iostream> #include<thread> #include<mutex> std::mutex mut; class A{ public: volatile int temp; A(){ temp=0; } void fun(int num){ int count=10; while(count>0){ mut.lock(); temp++; std::cout<<"thread_"<<num<<"...temp="<<temp<<std::endl; mut.unlock(); count--; } } void thread_run(){ std::thread t1(&A::fun,this,1); std::thread t2(&A::fun,this,2); t1.join(); t2.join(); } }; int main(){ A a; a.thread_run(); }
然后,我们说一下volatile关键字。
volatile和const关键很相似,都是修饰变量的,只是二者功能不一样。
volatile在多线程当中经常使用,因为在某一线程多次调用某一个变量,编译器会进行优化,将该变量存放在在寄存器当中,不会每次都从内存当中读入。果然该变量同时在其他线程当中被修改,这样就会发生脏读取错误。
而加上volatile修饰,则会提醒编译器,这个变量可能会被改变,不能存放到寄存器当中,需要每次都从内存当中读取。
最后,我们说一下join()和detach()的使用技巧。