C++ 多线程的语法以及使用
1. 线程的创建
首先创建一个多线程入口函数threadmain ,threadmain函数体中完成子线程所要做的事。
接着在主函数中创建线程对象th,调用构造函数,并传递一个函数指针作为入口函数:thread th(treadmain); 入口函数为thread 构造函数的参数。
之后在主线程中调用th.join()函数阻塞等待子线程结束,还可以使用this_thread::get_id()查看主线程和子线程分别对应的线程号。
代码:
#include<thread>
#include<iostream>
using namespace std;
void ThreadMain() {
cout << "begin sub thread id" <<this_thread::get_id()<< endl;
for (int i = 0; i < 10; i++) {
this_thread::sleep_for(100ms);
cout << "sub thread i " << i << endl;
}
cout << "end sub thread id" << this_thread::get_id() << endl;
}
int main(int arg,char* argv[]) {
thread th(ThreadMain);
cout << "main thread id" << this_thread::get_id() << endl;
cout << "begin wait thread id" << this_thread::get_id() << endl;
th.join();
cout << "end wait thread id" << this_thread::get_id() << endl;
return 0;
}
以上代码实现了简单的多线程的调用及实现
2. std::thread 对象的生命周期,以及线程的等待分离
- 不对线程对象进行维护时
{
thread th(thread);
}
此时程序将会报错,错误有多个原因,第一:主线程先一步退出;第二:子线程对象被销毁,而子线程还在运行。
2. 主线程阻塞等待子线程退出
{
thread th(ThreadMain);
th.join(); //主进程阻塞 等待子进程结束
}
主线程什么也做不了了,阻塞等待子线程结束。我们希望主线程与子线程同时运行,同时不想维护线程对象,这是我们可以使用 detach
3. 主线程和子线程分离
{
thread th(threadmain);
th.detach();
}
子线程与主线程分离,子线程成为守护进程[1],可能出现的问题:当主线程退出后,子线程不一定会退出,当子线程需要访问主线程的变量时,会报错。我们可以使子线程全部使用自身命名空间的变量来避免这种问题。但是这样一般会很麻烦。这是我们可以在主线程退出时,通知子线程结束
4. 设置变量通知子进程
bool is_exit=false;
void ThreadMain()
cout << "begin sub thread id" << this_thread::get_id() << endl;
for (int i = 0; i < 10; i++) {
if (is_exit) break;
this_thread::sleep_for(10ms);
cout << "sub thread i " << i << endl;
}
cout << "end sub thread id" << this_thread::get_id() << endl;
int main(){
{ thread th(ThreadMain);
this_thread::sleep_for(100ms); //使用变量通知子线程退出
is_exit=true;
th.join(); //主线程阻塞,等待子线程退出
}
}
入口函数中使用循环来模拟子线程长时间工作,设置了一个全局变量is_exit 用来通知,当主线程完成工作后,修改is_exit使子线程结束,并调用join() 回收子线程。
3.全局函数作为入口函数
如何传递参数
thread th(threadmain)构造函数基于模板函数,可以传递任意类型,当值传递时,调用构造函数时会进行一次拷贝,调用回调函数时会进行第二次拷贝。
#include<thread>
#include<iostream>
using namespace std;
class pase
{
public:
pase() {
cout << "created" << endl;
}
~pase() {
cout << "droped" << endl;
}
pase(const pase& i) {
name = i.name;
cout << "copied" << endl;
}
void setname(string p) {
name = p;
}
string getname() {
return name;
}
private:
string name;
}
void Thread(int p1,float p2,string p3,pase p4) {
cout << p1 << " " << p2 << " " << p3 << " " << p4.getname() << endl;
}
int main(int arc, char* argv[]) {
thread th;
{
string t = "time";
pase p;
p.setname(t);
//所有参数进行复制
th=thread (Thread, 100, 100.0,"string",p );
}
th.join();
}
上述代码的结果如下
我们可以看到pase对象进行了两次拷贝,三次析构
如果我们传递指针参数时,则只会在pase对象销毁时进行一次析构
void thread_ptr(pase* p) {
this_thread::sleep_for(100ms);
cout << "thread_main " << p->getname() << endl;
}
thread th;
{
pase p;
p.setname("john");
th=thread (thread_ptr, &p);
}
th.join();
结果如下:
使用指针传参时会带来一个问题:当指针所指向的空间销毁后,子进程可能还会用到这个指针。去访问已经销毁的空间。
下面是使用引用
void thread_ref(pase & p) {
this_thread::sleep_for(100ms);
cout << "thread_main " << p.getname() << endl;
}
{
pase p;
p.setname("john");
thread th(thread_ref, ref(p));
th.detach();
}
ref(p)标识为传引用,防止歧义
指针与引用类似
成员函数作为线程入口函数
成员函数如何作为线程入口函数
创建类 mythread 在类中创建成员函数来作为入口,创建线程对象,调用构造函数时,将成员函数指针以及对象的地址(this指针)传入
代码如下:
class mythread {
public:
void main(){
cout << "mythread name" << name << endl;
}
private:
string name="john";
int id = 001;
};
int main(){
mythread myth;
// 参数为成员函数指针,以及this指针(对象地址)
thread th(&mythread::main, &myth);
th.join();
}
如何封装成员函数
我们可以创建一个基类,基类中包括了如何创建,终止,回收进程的函数,可以帮助我们更好的维护进程。当我们需要使用多线程完成任务时,可以构造一个子类来继承该基类,同时在子类中重写虚函数,来作为线程的入口函数。
代码如下;
class xthread {
public:
void start() {
th_ = thread(&xthread::main, this);
}
virtual void main() =0;
void wait() {
if (th_.joinable())
th_.join();
}
void stop() {
exit = true;
wait();
}
bool is_exit() {
return exit;
}
private:
thread th_;
bool exit = false;
};
class testthread :public xthread {
public:
void main() override//确保虚函数被重写{
cout << "textthread "<<name << endl;
cout << "textthread begin" << endl;
while (!is_exit()) {
this_thread::sleep_for(200ms);
cout << "*"<<flush;
}
cout << "textthread end" << endl;
}
string name;
};
int main() {
testthread testth;
testth.name = "lili";
testth.start();
this_thread::sleep_for(2s);
testth.stop();
}
我们在基类中创建了创建进程的start()函数,终止进程的stop() 和wait()函数。在子类中重写了main函数作为线程入口。
lambda表达式作为线程入口函数
lambda表达式可以被称为匿名函数或临时函数,可以做一些简单的操作或运算。
lambda函数基本格式为[捕捉列表](参数)->返回值类型 函数体{}
首先演示lambda表达式作为普通函数时的用法:
int main(){
thread th([](){cout<<"lambda表达式";});
}
演示 lambda表达式作为成员函数
class test{
public:
void start{
thread th([this](){cout<<"testname"<<name;});
}
private:
string name;
}
守护进程(守护进程(daemon process)是在后台运行的一种特殊类型的进程,通常独立于控制终端,并在系统启动时启动。守护进程通常在后台执行任务,不与用户直接交互,且会在系统运行时一直存在。它们常用于执行系统级任务、服务和周期性的工作. ↩︎