C++多线程1

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 对象的生命周期,以及线程的等待分离

  1. 不对线程对象进行维护时
{
    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();
}

上述代码的结果如下
Alt text
我们可以看到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;

}

  1. 守护进程(守护进程(daemon process)是在后台运行的一种特殊类型的进程,通常独立于控制终端,并在系统启动时启动。守护进程通常在后台执行任务,不与用户直接交互,且会在系统运行时一直存在。它们常用于执行系统级任务、服务和周期性的工作. ↩︎

posted @ 2024-01-18 15:52  fangwanglong  阅读(7)  评论(0编辑  收藏  举报