C++ 多线程学习(1) ---- 线程的创建

线程和并发

计算机术语中的并发,指的是单个系统里同时执行多个独立的活动,而不是顺序一个个执行,对于单核CPU来说,某个时刻只可能处理一个任务,但是它却不是顺序执行的一个个任务,而是对CPU进行分时复用,一直在任务之间切换,每个任务完成一部分就去执行下一个任务,看起来任务在并行发生,虽然不是严格意义上执行多个任务,但是我们仍然称之为并发(concurrency)。真正的并发是在多核处理器上,能够真正同时执行多个任务,称为硬件并发(hardware concurrency)。

假设完成A和B任务都被分为5个大小相等,单核CPU交替的执行两个任务,每次执行其中的一块,其花费的时间并不是先完成A任务再玩成B任务所花费时间的两倍,而是要更多。因为系统从一个任务到另一个任务号需要完成一次上下文切换,这是需要时间的(图中的灰色块)。上下文切换需要操作系统为当前执行的任务保存状态和指令指针,由调度器计算出切换到哪个任务,并未要切换的任务加载处理器状态,然后将新任务的指令和数重新加载到缓存。

并发的方式分为多进程并发和多线程并发

多进程并发:这些独立的进程可以通过常规的进程间通信方式进行通信,比如管道,消息队列,共享内存,信号量,存储映射I/O,套接字等,缺点是进程间通信较为复杂,速度相对线程间的通信也比较慢,启动进程的开销比线程大,使用的系统资源也更多。

多线程并发:线程类似轻量级的进程,但是一个进程中所有线程都共享相同的地址空间,线程间的大部分数据都可以共享,线程间的通信一般都可以通过全局变量来实现。

C++ 11 多线程库

C++ 98标准中并没有线程库的实现,而在C++ 11中提供了多线程的标准库,提供了管理线程,保护共享数据,线程间的同步操作,原子操作的功能。

使用普通函数或者静态函数创建线程

//  static void threadLoop();
//  使用静态函数的方式构造
void threadDemo::threadLoop() {
	int i = 0;
	while (true) {
		if (i > 10) {
			cout << "exit thread" << endl;
			break;
			//this_thread::yield(); // 让出CPU的使用权
		}

		cout << "threadDemo::threadLoop thread tid = " << this_thread::get_id() << endl;
		
		//  microseconds 表示 us
		//this_thread::sleep_for(chrono::microseconds(2000));
		this_thread::sleep_for(chrono::milliseconds(1000));
		i++;
	}
}

//  使用普通函数的方式构造
void _threadloopsleep() {
	while (true) {
		cout << "_threadloopsleep thread tid = " << this_thread::get_id() << endl;
		cout << "cpu count: " << std::thread::hardware_concurrency() << endl;
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}


//  使用静态函数方式创建
{
	t = new thread(threadLoop);
	t->join();
}

{
//  使用普通函数的方式创建
	t = new thread(_threadloopsleep);
	t->join();
    cout << "thread get handle " << t->native_handle() << endl;
}
  • 首先创建的一个new 一个线程对象得到一个 std::thread类型的指针 t,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完毕退出,整个线程也就执行完成了
  • 线程创建成功后,就会立刻启动,没有一个类似于 start 的函数来显式启动线程。
  • 一旦线程开始运行,就要在主函数中显式决定是否要等待它启动完成(join),或者分离它让它自己运行(detach),只需要在 thread 对象销毁前完成这个决定就可以了,指针 t 是栈上的变量,只需要在 main 函数执行完成之前决定即可。
  • join 函数时主线程阻塞,等待子线程执行完成,join 的另一个任务就是回收该线程中资源

C++11 还提供了获取线程 id,获取系统CPU个数,获取thread 的 native_handle (handle 可以用于 pthread 相关操作),使得线程休眠等功能。

调用线程的 detach 函数,可以将线程放在后台运行,所有权和控制权被转交给C++ 运行时库,以确保线程相关的资源能够在线程退出时正确的回收。这个模式类型于 UNIX 中守护进程的概念(damon),线程被分离后,即使该线程对象被析构了,线程还是能够在后台运行,但是由于线程对象被析构了,主线程不能通过该对象名和这个线程进行通信。

传递参数给线程

线程的入口函数不带参数,也可以从主线程传递参数给子线程,这是传递的参数需要用 ref 函数包装起来

void threadDemo::threadLoopPara(int& para) {
	while (true) {
		cout << "thread tid = " << this_thread::get_id() << " get para = " << para << endl;
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}

{
	int temp = 10;
	t = new thread(threadLoopPara, ref(temp));
	t->join();
}

注意传递的参数的生命周期,传递的参数 temp,会在主函数执行完毕之后被回收,假设上面的线程使用的是线程分离的模式运行,打印出的 temp 将是错误的结果。

sleep_for 和 sleep_util 函数

//  固定时间间隔来执行程序 using sleep_until
/*_threadloopsleeputil thread tid = 12664
_threadloopsleeputil duration 1005
_threadloopsleeputil thread tid = 12664
_threadloopsleeputil duration 1014
_threadloopsleeputil thread tid = 12664
_threadloopsleeputil duration 1014
_threadloopsleeputil thread tid = 12664
_threadloopsleeputil duration 1015
_threadloopsleeputil thread tid = 12664*/
void _threadloopsleeputil() {
	//using std::chrono::operator""ms;
	chrono::time_point<chrono::steady_clock> awaketime;
	static clock_t lastclock = clock();
	while (true) {
		{
			clock_t duration = clock() - lastclock;
			cout << "_threadloopsleeputil duration " << duration << endl;
			lastclock = clock();
		}

		chrono::time_point<chrono::steady_clock> time_now = chrono::steady_clock::now();
		awaketime = time_now + 1000ms;

		cout << "_threadloopsleeputil thread tid = " << this_thread::get_id() << endl;
		this_thread::sleep_until(awaketime);
	}
}

使用 sleep_util 函数可以使线程休眠到特定的时间点唤醒,sleep_for 是让线程休眠特定的时间段。

使用仿函数创建线程

仿函数(Functor) 又称为函数对象 (Function Object) 是一个能行使函数功能的类,仿函数的语法几乎和我们普通的函数调用是一致的,特殊的是作为一个类,必须重载 operator() 运算符,因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

需要注意的是,如果线程的入口函数使用了仿函数中的变量,线程使用分离的方式运行,要注意变量的生命周期导致的变量值失效的问题。

class loopMain {
public:
	loopMain(const char *name);
	~loopMain() = default;
	void operator()();
private:
	string mName;
};

loopMain::loopMain(const char *name): mName(name){
	cout << "construct " << mName << endl;
}


void loopMain::operator()() {
	while (true) {
		cout << "loopMain  operator()" << endl;
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}

void threadDemo::threadLoopMain() {
	while (true) {
		cout << "method threadLoopMain" << endl;
		this_thread::sleep_for(chrono::milliseconds(1000));
	}

}

{
	loopMain loop("mLoop");
	t = new thread(loop);
	t->join();
}

使用成员函数创建线程

使用类型的成员函数的方式创建线程,此时注意必须要先实例化对象,然后传入类的实例化对象,如果变量以分离的方式运行,要注意变量的生命周期导致的变量值失效的问题。

public:
void threadLoopMain();


void threadDemo::threadLoopMain() {
	while (true) {
		cout << "method threadLoopMain get var:" << m << endl;
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}

{
	t = new thread(&threadDemo::threadLoopMain, this);
	t->detach();
}

使用lamda表达式创建线程

lamda 本质上是一种匿名函数类型,函数类型包含了函数指针的功能,所以也可以用于创建线程。

	{
		int temp = 100;
		t = new thread([&]() {
			while (true) {
				cout << "thread tid = " << this_thread::get_id() << " get para = " << temp << endl;
				this_thread::sleep_for(chrono::milliseconds(1000));
			}
		});
		t->join();
	}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值