C++11新特性——线程

在C++11之前,C++没有对线程提供语言级别的支持,各种操作系统和编译器实现线程的方法不一样。

C++11增加了线程以及线程相关的类,统一编程风格、简单易用、跨平台。

一、创建线程

头文件:#include <thread>

线程类:std::thread

构造函数:

1)thread() noexcept;

默认构造函,构造一个线程对象,不执行任何任务(不会创建/启动子线程)。

2)template< class Function, class... Args > (常用)

explicit thread(Function&& fx, Args&&... args );

创建线程对象,在线程中执行任务函数fx中的代码,args是要传递给任务函数fx的参数。

任务函数fx可以是普通函数、类的非静态成员函数、类的静态成员函数、lambda函数、仿函数。

3)thread(const thread& ) = delete;

删除拷贝构造函数,不允许线程对象之间的拷贝。

4)thread(thread&& other ) noexcept;

移动构造函数,将线程other的资源所有权转移给新创建的线程对象。

赋值函数:

thread& operator= (thread&& other) noexcept;

thread& operator= (const other&) = delete;

线程中的资源不能被复制,如果other是右值,会进行资源所有权的转移,如果other是左值,禁止拷贝。

注意:

  1. 先创建的子线程不一定跑得最快(程序运行的速度有很大的偶然性)。
  2. 线程的任务函数返回后,子线程将终止。
  3. 如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。

示例:

#include<iostream>
#include<thread>
using namespace std;

//普通函数
void func(int num,const string &str)
{
	for(int i=0;i<10;i++)
	{
		cout<<num<<str<<endl;
		this_thread::sleep_for(chrono::seconds(1));//休眠一秒
	}
}

//仿函数
class MyThread1
{
public:
	void operator()(int num,const string &str)
	{
		for(int i=0;i<10;i++)
		{
			cout<<num<<str<<endl;
			this_thread::sleep_for(chrono::seconds(1));
		}
	}
};

//静态成员函数
class MyThread2
{
public:
	static void func(int num,const string &str);
};
void MyThread2::func(int num,const string &str)
{
	for(int i=0;i<10;i++)
	{
		cout<<num<<str<<endl;
		this_thread::sleep_for(chrono::seconds(1));
	}
}

//非静态成员函数
class MyThread3
{
public:
	void func(int num,const string &str)
	{
		for(int i=0;i<10;i++)
		{
			cout<<num<<str<<endl;
			this_thread::sleep_for(chrono::seconds(1));
		}
	}
};

int main()
{
	//lambda函数
	auto lam_func = [](int num,const string&str)
	{
		for(int i=0;i<10;i++)
		{
			cout<<num<<str<<endl;
			this_thread::sleep_for(chrono::seconds(1));
		}
	};
	
	//普通函数创建线程
	//thread t1(func,1,"普通函数");
	
	//仿函数创建线程
	//thread t2(MyThread1(),2,"仿函数");
	
	//静态成员函数创建线程
	//thread t3(MyThread2::func,3,"静态成员函数");
	
	//非静态成员函数创建线程
	//MyThread3 myth;//必须先创建对象,且对象生命周期比线程长
	//thread t4(&MyThread3::func,&myth,4,"非静态成员函数");//第三个参数必须填对象的this指针,否则会拷贝对象
	
	//lambda函数创建线程
	thread t5(lam_func,5,"lambda函数");
	
	//主线程
	for(int i=0;i<10;i++)
	{
		cout<<"主线程\n";
		this_thread::sleep_for(chrono::seconds(1));
	}
	
	//回收线程
	//t1.join();
	//t2.join();
	//t3.join();
	//t4.join();
	t5.join();
	return 0;
}

二、线程资源的回收

虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自己私有的栈空间。所以,线程结束时需要回收资源。

回收子线程的资源有两种方法:

1)在主程序中,调用join()成员函数等待子线程退出,回收它的资源。如果子线程已退出,join()函数立即返回,否则会阻塞等待,直到子线程退出。

2)在主程序中,调用detach()成员函数分离子线程,子线程退出时,系统将自动回收资源。分离后的子线程不可join()

joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。

示例:

#include <iostream>
#include <thread>                // 线程类头文件。
#include <windows.h>         // Sleep()函数需要这个头文件。
using namespace std;

// 普通函数。
void func(int num, const string& str) {
      for (int ii = 1; ii <= 10; ii++)
      {
            cout << num << str << endl;
            Sleep(1000);   // 休眠1秒。
      }
}

int main()
{
      // 用普通函数创建线程。
      thread t1(func, 3, "线程1");
      thread t2(func, 8, "线程2");

      t1.detach(); t2.detach();  // 分离子线程。

      //cout << "任务开始。\n";
      //for (int ii = 0; ii < 12; ii++) {
      //   cout << "执行任务中......\n";
      //   Sleep(1000);   // 假设执行任务需要时间。
      //}
      //cout << "任务完成。\n";

      //t1.join();         // 回收线程t1的资源。
      //t2.join();         // 回收线程t2的资源。
      Sleep(12000);        //子线程分离出去后主线程不能退出,等待子线程运行完再退出
      return 0;
}

三、this_thread的全局函数

C++11提供了命名空间this_thread来表示当前线程,该命名空间中有四个函数:get_id()、sleep_for()、sleep_until()、yield()

1)get_id()

thread::id get_id() noexcept;

该函数用于获取线程ID,thread类也有同名的成员函数。

2)sleep_for()  VS  Sleep(1000)   Linux sleep(1)

template <class Rep, class Period>

  void sleep_for (const chrono::duration<Rep,Period>& rel_time);

该函数让线程休眠一段时间。

3)sleep_until()          2022-01-01 12:30:35

template <class Clock, class Duration>

  void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);

该函数让线程休眠至指定时间点。(可实现定时任务)

4)yield()

void yield() noexcept;

该函数让线程主动让出自己已经抢到的CPU时间片。

5)thread类其它的成员函数

void swap(std::thread& other);    // 交换两个线程对象。

static unsigned hardware_concurrency() noexcept;   // 返回硬件线程上下文的数量。

The interpretation of this value is system- andimplementation- specific, and may not be exact, but just an approximation.

Note that this does not need to match the actualnumber of processors or cores available in the system: A system can supportmultiple threads per processing unit, or restrict the access to its resourcesto the program.

If this value is not computable or well defined,the function returns 0.

示例:

#include <iostream>
#include <thread>                // 线程类头文件。
using namespace std;

// 普通函数。
void func(int num, const string& str) {
      cout << "子线程:" << this_thread::get_id() << endl;

      for (int ii = 1; ii <= 3; ii++)
      {
            cout << num << str << endl;
            this_thread::sleep_for(chrono::seconds(1));    // 休眠1秒。
      }
}

int main()
{
      // 用普通函数创建线程。
      thread t1(func, 1, "子线程。");
      thread t2(func, 2, "子线程。");

      cout << "主线程:" << this_thread::get_id() << endl;
      cout << "线程t1:" << t1.get_id() << endl;
      cout << "线程t2:" << t2.get_id() << endl;

      t1.join();         // 回收线程t1的资源。
      t2.join();         // 回收线程t2的资源。
      return 0;
}

四、call_once函数

在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一次。

在线程的任务函数中,可以用std::call_once()来保证某个函数只被调用一次。

头文件:#include <mutex>

template< class callable, class... Args >

  void call_once( std::once_flag& flag, Function&& fx, Args&&... args );

第一个参数是std::once_flag,用于标记函数fx是否已经被执行过。

第二个参数是需要执行的函数fx

后面的可变参数是传递给函数fx的参数。

示例:

#include <iostream>
#include <thread>        // 线程类头文件。
#include <mutex>        // std::once_flag和std::call_once()函数需要包含这个头文件。
using namespace std;

once_flag onceflag;       // once_flag全局变量。本质是取值为0和1的锁。
// 在线程中,打算只调用一次的函数。
void once_func(const int bh, const string& str)  {
      cout << "once_func() bh= " << bh << ", str=" << str << endl;
}

// 普通函数。
void func(int bh, const string& str) {
      call_once(onceflag,once_func,0, "开始。");

      for (int ii = 1; ii <= 3; ii++)
      {
            cout << bh << str << endl;
            this_thread::sleep_for(chrono::seconds(1));    // 休眠1秒。
      }
}

int main()
{
      // 用普通函数创建线程。
      thread t1(func, 3, "普通函数");
      thread t2(func, 8, "普通函数");

      t1.join();         // 回收线程t1的资源。
      t2.join();         // 回收线程t2的资源。
      return 0;
}

五、native_handle函数

C++11定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行封装,会损失一部分功能。

为了弥补C++11线程库的不足,thread类提供了native_handle()成员函数,用于获得与操作系统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。

示例:

#include <iostream>
#include <thread>
#include <pthread.h>        // Linux的pthread线程库头文件。
using namespace std;

void func()    // 线程任务函数。
{
  for (int ii=1;ii<=10;ii++)
  {
    cout << "ii=" << ii << endl;
    this_thread::sleep_for(chrono::seconds(1));    // 休眠1秒。
  }
}

int main()
{
  thread tt(func);          // 创建线程。
  this_thread::sleep_for(chrono::seconds(5));    // 休眠5秒。
  pthread_t thid= tt.native_handle();  // 获取Linux操作系统原生的线程句柄。
  pthread_cancel(thid);  // 取消线程。
  tt.join();   // 等待线程退出。
  return 0;
}

六、线程安全

同一进程中的多个线程共享该进程中的全部系统资源,当多个线程同时调用一个资源时会产生冲突。

关于线程安全需知道的几个概念:

1)顺序性:程序按照代码的先后顺序执行,但CPU为了提高效率,可能会进行优化,按更高效的顺序执行.

2)可见性:线程操作共享变量时,会将该变量从内存加载到CPU缓存中,修改该变量后,CPU会立即更新缓存,但不一定会立即将它写回内存.这时其他线程访问该变量时还是旧值.                                      当多个线程并发访问共享变量时,一个线程对其修改,其他线程能立马看见.

3)原子性:CPU执行指令的顺序为:读取指令、读取内存、执行指令、写回内存。                               原子操作的意思就是一个操作(可能含多个步骤)要么全部执行,要么都不执行.

如何保证线程安全:

1.volatile关键字:                                                                                                                               1)保证内存变量的可见性.                                                                                                                   2)禁止代码优化(保证顺序性).

2.原子操作(原子类型)    详情见该文章:(6条消息) C++11新特性——互斥锁、条件变量、原子类型_SatoshiGogo的博客-CSDN博客

3.线程同步(锁)               详情见该文章:(6条消息) C++11新特性——互斥锁、条件变量、原子类型_SatoshiGogo的博客-CSDN博客

示例:

#include <iostream>
#include <thread>        // 线程类头文件。
using namespace std;

int aa = 0;     // 定义全局变量。

// 普通函数,把全局变量aa加1000000次。
void func() {
      for (int ii = 1; ii <= 1000000; ii++)
            aa++;
}

int main()
{
      // 用普通函数创建线程。
      thread t1(func);     // 创建线程t1,把全局变量aa加1000000次。
      thread t2(func);     // 创建线程t2,把全局变量aa加1000000次。
                           //两个线程操作不安全,加不到2000000

      t1.join();         // 回收线程t1的资源。
      t2.join();         // 回收线程t2的资源。

      cout << "aa=" << aa << endl;   // 显示全局变量aa的值。
      return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值