进程线程
进程:正在进行的可执行程序
线程:进程中的执行路线
关系:一个进程必然包含一个主线程,同一个进程中所有的线程共享内存。生成一个进程时其主线程自动从main()函数开始运行,当主线程从main()函数返回时,则整个进程执行完毕。 分线程也需要从一个函数开始运行,此函数运行完毕时分线程执行完毕。
并发方法:多进程并发与多进程并发。
C++11中的thread库
#include <thread>
#include <iostream>
using namespace std;
创建线程的方法 : std::thread …(a)
线程类参数 a 是一个可调用对象。一组可执行的语句称为可调用对象,c++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象。
1.函数创建
void myPrint()
{
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main()
{
//(1)创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(myPrint);
//(2)阻塞主线程并等待myPrint执行完,当myPrint执行完毕,join()就执行完毕,主线程继续往下执行
//join意为汇合,子线程和主线程汇合
myThread.join();
//detach:分离,主线程不再与子线程汇合,不再等待子线程
//detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
//myThread.detach();
//(4)joinable()判断是否可以成功使用join()或者detach()
//如果返回true,证明可以调用join()或者detach()
//如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了
if (myThread.joinable())
{
cout << "可以调用可以调用join()或者detach()" << endl;
}
else
{
cout << "不能调用可以调用join()或者detach()" << endl;
}
cout << "Hello World!" << endl;
return 0;
}
2.创建一个类,并编写圆括号重载函数,初始化一个该类的对象,把该对象作为线程入口地址 (重载了函数调用运算符的类对象)
class Ta
{
public:
void operator()() //不能带参数
{
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
}
};
//main函数里的:
Ta ta;
thread myThread(ta);//此时传递的类实例 ta不能用 &ta 来代替 ref(ta)
myThread.join();
3.lambda表达式创建线程
//main函数中
auto lambdaThread = [] {
cout << "我的线程开始执行了" << endl;
//-------------
//-------------
cout << "我的线程开始执行了" << endl;
};
thread myThread(lambdaThread);
myThread.join();
4.把某个类中的某个函数作为线程的入口地址
class Data_
{
public:
void GetMsg(){}
void SaveMsh(){}
};
//main函数里
Data_ s;
//第一个&意思是取址;第二个&意思是引用,相当于std::ref(s),此种情况可以用 & 代替 ref() ,想让主线程创建子线程时不进行复制构造,尽量使用ref()函数,不用 & ,当用类成员函数作为子线程入口时,传递的类实例参数相当于一个this指针,因此可以用 & 代替 std::ref(s)。
//thread oneobj(&Data_::SaveMsh,s)传值也是可以的
//在其他的构造函数中&obj是不会代表引用的,会被当成取地址
//调用方式:对象成员函数地址,类实例,[成员函数参数]
//第二个参数可以传递对象s,也可以传递引用std::ref(s)或&s
//传递s,会调用拷贝构造函数在子线程中生成一个新的对象
//传递&,子线程中还是用的原来的对象,不再传递复制的临时对象,所以就不能detach,因为主线程运行完毕会把该对象释放掉
thread oneobj(&Data_::SaveMsh,&s);
thread twoobj(&Data_::GetMsg,&s);
oneobj.join();
twoobj.join();
线程传递参数注意事项
1.thread调用
#include <iostream>
#include <thread>
using namespace std;
void myPrint(const int &i, char* pmybuf)
{
//如果线程从主线程detach了
//thread创建函数分线程时对于分线程函数调用的参数采取复制的方法,即另外创建一个临时副本,将副本用于分线程函数参数调用,复制的过程是在主线程中进行的,之后将临时副本传递给分线程,继续执行主线程和开始分线程,如果thread构造函数时如果其参数需要进行显式类型转换或显式调用构造函数创建临时对象的话这两步是在主线程中进行,之后对处理后的参数进行复制,将复制的副本传递给分线程,这一步也是在主线程中进行,之后继续执行主线程和开始分线程。
//i不是mvar真正的引用,实际上只是值传递,引用的是复制的临时副本,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用。
//推荐改为const int i
cout << i << endl;
//而经过复制之后,pmybuf的值还是指向原来的地址,即指向原来的字符串,是一种浅复制,所以这么写是不安全的
cout << pmybuf << endl;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar, mybuf);//第一个参数是函数名,后两个参数是函数的参数;
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
//由于从主线程传递给分线程的参数都是另外创建的临时副本(右值类型),因此分线程的函数参数必须都是 const 类型参数,一些类对象等参数必须是 const *** & 类型,否则会报错
2.使用detach()的注意事项
#include <iostream>
#include <thread>
#include <string>
using namespace std;
void myPrint(const int i, const string& pmybuf)
{
cout << i << endl;
cout << pmybuf << endl;
}
int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test";
//如果detach了,这样仍然是不安全的
//主线程中会对 mybuf 的值进行复制,然后传入子线程中用于参数的调用,对于指针来说这种复制是浅复制,然后在子线程中系统采用mybuf隐式类型转换成string,再继续子线程运行。如果主线程运行完了,mybuf指向的地址内容就会被回收了,子线程若没有运行完就会出错。
//推荐先创建一个临时对象thread myThread(myPrint, mvar, string(mybuf));此时主线程调用thread构造函数时,会先在主线程中进行 string(mybuf) 的类型转换,然后对转换的string临时对象进行复制创建另一个临时副本传入分线程,此时分线程就绝对安全了。
thread myThread(myPrint, mvar, mybuf);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
如果传递int这种简单类型,推荐使用值传递,不要用引用
如果传递类对象,避免使用隐式类型转换,采用在主线程中就创建出临时对象用于子线程参数调用,然后在子线程函数参数里用引用来接,否则还会创建出一个对象。
3.线程参数
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
mutable int m_i; //m_i即使实在const中也可以被修改
A(int i) :m_i(i) {}
};
void myPrint(const A& pmybuf)
{
pmybuf.m_i = 199;
cout << "子线程myPrint的参数地址是" << &pmybuf << "thread = " << this_thread::get_id() << endl;
}
线程id:
id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字都不一样。线程id可以用C++标准库里的函数 std::this_thread::get_id() 来获取。
4.std::ref()
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
mutable int m_i; //m_i即使实在const中也可以被修改
A(int i) :m_i(i) {}
};
void myPrint(const A& pmybuf)
{
pmybuf.m_i = 199;
cout << "子线程myPrint的参数地址是" << &pmybuf << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
A myObj(10);
//myPrint(const A& pmybuf)中引用不能去掉,如果去掉会多创建一个对象
//const也不能去掉,去掉会出错
//传递的const引用,引用的是主线程中调用拷贝构造函数构造的一个新的临时对象,
//所以在子线程中修改m_i的值不会影响到主线程
//如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(myPrint, std::ref(myObj));
//这样const就是真的引用了,myPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了
thread myThread(myPrint, myObj);
myThread.join();
//myThread.detach();
cout << "Hello World!" << endl;
}
5. unique_ptr 智能指针
#include <iostream>
#include <thread>
#include <memory>
using namespace std;
void myPrint(unique_ptr<int> ptn)
{
cout << "thread = " << std::this_thread::get_id() << endl;
}
int main()
{
unique_ptr<int> up(new int(10));
//独占式指针只能通过std::move()才可以传递给另一个指针
//传递后up就指向空,新的ptn指向原来的内存
//所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
thread myThread(myPrint, std::move(up));
myThread.join();
//myThread.detach();
return 0;
}
互斥、锁
mutex: 互斥量
互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。