C++面向对象多线程编程简介
2016-06-29 11:32
387 查看
多线程编程引发的问题:死锁、无限延迟、数据竞争等。并发编程包括多任务(多进程)和多线程。
使用多线程编程时需包含头文件<thread>,具体测试程序代码如下:
以下为数据竞争和互斥对象的说明实例,使用互斥锁需要<mutex>头文件,最基础的方法就是使用lock和unlock方法,使中间部分为临界区,不过这种方法存在临界区部分抛出异常的问题,因此提出了lock_guard<mutex> guard(mu);的方法,能够在临界区部分抛出异常后解锁,不过由于cout函数是公开的所以依然不能保证打印准确,所以建立一个写文件类保护输出对象,同时成员变量mutex对一些函数的访问可以实现互斥。
死锁问题:下例说明了出现死锁的情况,当临界区要求使用2个及以上的互斥锁时,若加锁的顺序不一致,可能导致死锁。因此为避免死锁程序猿要保证加锁的顺序一致,避免发生死锁,也可以使用C++库函数lock函数按照顺序预先加锁,再使用lock_guard智能解锁。
以下为unique_lock和once_flag的使用实例说明,不过unique_lock相对于lock_guard开销更大。
#include <iostream>
#include <thread>
#include <mutex>
#include <fstream>
using namespace std;
class lofFile{
public:
lofFile(){
//fout.open("log.txt");
}
void shared_print(string str,int i){
call_once(m_flag,[&](){fout.open("log.txt");});//使用once_flag可以只调用依次打开文件函数,从而节省开销
unique_lock<mutex> locker(m_mu);//unique_lock相对于lock_guard具有更好的弹性,从而 能够给确定的代码段加锁
//同时,其对象传递时交出所有权,使用defer_lock参数后,就可以使用lock和unlock实现弹性加锁
fout<<str<<":"<<i<<endl;
locker.unlock();
//.......
unique_lock<mutex> locker2(m_mu,defer_lock);
//.......
locker2.lock();
//.......
locker2.unlock();
//.......
}
private:
mutex m_mu;
once_flag m_flag;
ofstream fout;
};
void fun2(lofFile& lof){
for(int i=0;i<10;i++)
lof.shared_print("fun2",i);
}
int main(){
lofFile lof;
thread t2(fun2,ref(lof));
for(int i=0;i>-10;i--)
lof.shared_print("main",i);
t2.join();
}
使用多线程编程时需包含头文件<thread>,具体测试程序代码如下:
#include <iostream> #include <thread> using namespace std; void function_1(){ cout<<"Hello world!"<<endl; } void function_2(string str){ cout<<str<<endl; } void function_3(string& str){ str="我修改了,哈哈!"; } void function_4(string str){ //str="我修改了,哈哈!"; cout<<str<<endl; } class factor{ public: void operator()(){ cout<<"Thread factor run"<<endl; } }; int main(){ thread t1(function_1);//通过函数名创建线程 cout<<this_thread::get_id()<<endl; //主线程的id cout<<t1.get_id()<<endl;//t1线程的id factor fac; thread t2(fac); //通过类对象创建线程 //thread t2(factor());//通过调用类函数创建线程 thread t3(function_2,"你好,世界!"); //带有值传递的创建线程 string text="woxiugaile,hehe"; thread t5(function_4,move(text));//带有移动传递的创建线程,text被移动后为空 t5.join();//若t5还没有执行完成,则主线程等待t5执行完毕再执行。 cout<<text<<endl; thread t4(function_3,ref(text));//若想传递引用时,实参需使用ref(),同时函数形参为&类型 ,能否实现线程间共享内存管理 t4.join(); cout<<text<<endl; for(int i=0;i<10;i++){ cout<<"From main: "<<i<<endl; } t3.join(); t2.detach();//分离:线程t2与主线程分别进行互不干扰。 t1.detach(); for(int i=0;i<100;i++){ } if(t1.joinable()){//在join之前判断线程是否可以join,一般detach之后就不能再join t1.join(); } cout<<thread::hardware_concurrency()<<endl;//输出计算机的核数,即并发处理时的优化效率最高的最大线程数量 }
以下为数据竞争和互斥对象的说明实例,使用互斥锁需要<mutex>头文件,最基础的方法就是使用lock和unlock方法,使中间部分为临界区,不过这种方法存在临界区部分抛出异常的问题,因此提出了lock_guard<mutex> guard(mu);的方法,能够在临界区部分抛出异常后解锁,不过由于cout函数是公开的所以依然不能保证打印准确,所以建立一个写文件类保护输出对象,同时成员变量mutex对一些函数的访问可以实现互斥。
#include <iostream> #include <thread> #include <mutex> #include <fstream> using namespace std; mutex mu; void shared_print(string str,int i){ lock_guard<mutex> guard(mu);//尽量使用这种保护的互斥锁,因为能够避免加锁后cout抛出异常的情况,lock_guard能够在抛出异常时自动解锁 //mu.lock();//使用mutex互斥锁 ,不过这种直接使用lock和unlock的方式不安全,若出现异常就完了 cout<<str<<":"<<i<<endl; //mu.unlock();//解锁 } void fun1(){ for(int i=0;i<10;i++) shared_print("fun1",i); } //以上使用互斥锁虽然能保证函数访问时互斥,不过cout的调用不能保证,所以建立一个输出类,使得写文件对象保证互斥访问。 class lofFile{ public: lofFile(){ fout.open("log.txt"); } void shared_print(string str,int i){ lock_guard<mutex> guard(m_mu); fout<<str<<":"<<i<<endl; } private: mutex m_mu; ofstream fout; }; void fun2(lofFile& lof){ for(int i=0;i<10;i++) lof.shared_print("fun2",i); } int main(){ thread t1(fun1); for(int i=0;i>-10;i--) shared_print("main",i); t1.join();//确保t1进程进行完毕 lofFile lof; thread t2(fun2,ref(lof)); for(int i=0;i>-10;i--) lof.shared_print("main",i); t2.join(); }
死锁问题:下例说明了出现死锁的情况,当临界区要求使用2个及以上的互斥锁时,若加锁的顺序不一致,可能导致死锁。因此为避免死锁程序猿要保证加锁的顺序一致,避免发生死锁,也可以使用C++库函数lock函数按照顺序预先加锁,再使用lock_guard智能解锁。
#include <iostream> #include <thread> #include <mutex> #include <fstream> using namespace std; class lofFile{ public: lofFile(){ fout.open("log.txt"); } void shared_print(string str,int i){ lock(m_mu,m_mu2);//当一些临界区需要2个及以上的互斥锁时,要保证这些不同临界区的互斥锁的加锁顺序要一致,否则可能导致死锁 , //为解决这一问题使用lock()函数提前说明加锁顺序,则lock_guard参数adopt_lock说明已经加锁,只需只能解锁即可 lock_guard<mutex> guard(m_mu,adopt_lock); lock_guard<mutex> guard2(m_mu2,adopt_lock); cout<<str<<":"<<i<<endl; } void shared_print2(string str,int i){ lock(m_mu,m_mu2);//若不使用lock函数说明加锁顺序也可以,但必须保证以下互斥锁的加锁顺序一致,避免出现死锁情况 lock_guard<mutex> guard(m_mu2,adopt_lock); lock_guard<mutex> guard2(m_mu,adopt_lock); cout<<str<<":"<<i<<endl; } private: mutex m_mu; mutex m_mu2; ofstream fout; }; void fun2(lofFile& lof){ for(int i=0;i<10;i++) lof.shared_print2("fun2",i); } int main(){ lofFile lof; thread t2(fun2,ref(lof)); for(int i=0;i>-10;i--) lof.shared_print("main",i); t2.join(); }导致死锁的情况:程序一直被挂起。。。
以下为unique_lock和once_flag的使用实例说明,不过unique_lock相对于lock_guard开销更大。
#include <iostream>
#include <thread>
#include <mutex>
#include <fstream>
using namespace std;
class lofFile{
public:
lofFile(){
//fout.open("log.txt");
}
void shared_print(string str,int i){
call_once(m_flag,[&](){fout.open("log.txt");});//使用once_flag可以只调用依次打开文件函数,从而节省开销
unique_lock<mutex> locker(m_mu);//unique_lock相对于lock_guard具有更好的弹性,从而 能够给确定的代码段加锁
//同时,其对象传递时交出所有权,使用defer_lock参数后,就可以使用lock和unlock实现弹性加锁
fout<<str<<":"<<i<<endl;
locker.unlock();
//.......
unique_lock<mutex> locker2(m_mu,defer_lock);
//.......
locker2.lock();
//.......
locker2.unlock();
//.......
}
private:
mutex m_mu;
once_flag m_flag;
ofstream fout;
};
void fun2(lofFile& lof){
for(int i=0;i<10;i++)
lof.shared_print("fun2",i);
}
int main(){
lofFile lof;
thread t2(fun2,ref(lof));
for(int i=0;i>-10;i--)
lof.shared_print("main",i);
t2.join();
}
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Python3写爬虫(四)多线程实现数据爬取
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- C#实现多线程的同步方法实例分析
- Lua中调用C++函数示例
- 浅谈chuck-lua中的多线程
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- 解析C#多线程编程中异步多线程的实现及线程池的使用
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C++联合体转换成C#结构的实现方法
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析