【C++ 语言】线程 ( 线程创建方法 | 线程标识符 | 线程属性 | 线程属性初始化 | 线程属性销毁 | 分离线程 | 线程调度策略 | 线程优先级 | 线程等待 )

250 篇文章 160 订阅



I 线程创建方法


1. 线程创建方法函数原型 : int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, (void*)(*start_rtn)(void*), void *arg);

2. pthread_create 方法的 4 个参数 ;

  • 参数 1 ( pthread_t *tidp ) : 线程标识符指针 , 该指针指向线程标识符 ;
  • 参数 2 ( const pthread_attr_t *attr ) : 线程属性指针 ;
  • 参数 3 ( (void*)(*start_rtn)(void*) ) : 线程运行函数指针 , start_rtn 是一个函数指针 , 其参数和返回值类型是 void* 类型 ;
  • 参数 4 ( void *arg ) : 参数 3 中的线程运行函数的参数 ;

3. 返回值说明 :

  • 线程创建成功 , 返回 0 ;
  • 线程创建失败 , 返回 错误代码 ;

4. 关于函数指针参数的说明 : C++ 中函数指针类型是 void *(PTW32_CDECL *start) (void *)

  • 函数的参数类型是 void* 指针 ;
  • 函数的返回值类型 void* 指针 ;

5. 函数多参数方案 : 如果线程执行的函数有多个参数 , 可以使用结构体 , 类进行封装 ;

6. 线程属性 : 创建线程时 , 给线程指定属性 pthread_attr_t 是结构体类型 ;

7. 代码示例 :

	/*
		线程创建方法函数原型 : 
		int pthread_create(
			pthread_t *tidp, 
			const pthread_attr_t *attr, 
			(void*)(*start_rtn)(void*), 
			void *arg);
		
		该方法需要提供四个参数 ;
			参数 1 ( pthread_t *tidp ) :线程标识符指针 , 该指针指向线程标识符 ;
			参数 2 ( const pthread_attr_t *attr ) : 线程属性指针 ;
			参数 3 ( (void*)(*start_rtn)(void*) ) : 线程运行函数指针 , start_rtn 是一个函数指针 , 
				其参数和返回值类型是 void* 类型
			参数 4 ( void *arg ) : 参数 3 中的线程运行函数的参数 ;
		返回值 :
			线程创建成功 , 返回 0 ;
			线程创建失败 , 返回 错误代码 ;

		关于函数指针参数 : C++ 中函数指针类型是 void *(PTW32_CDECL *start) (void *) ,
			函数的参数类型是 void* 指针
			函数的返回值类型 void* 指针

		函数多参数方案 : 如果线程执行的函数有多个参数 , 可以使用结构体 , 类进行封装

		线程属性 : 创建线程时 , 给线程指定属性 pthread_attr_t 是结构体类型

	*/
	//函数指针 函数名 和 &函数名 都可以作为函数指针
	pthread_create(&pid , &attribute, pthread_function, hello);


II 线程执行函数


1. 线程执行函数的要求 : C++ 中规定线程执行函数的函数指针类型是 void *(PTW32_CDECL *start) (void *) ;

2. 函数作用 : 将该函数的指针作为线程创建方法 pthread_create 的第三个参数 ;

3. 参数处理 : 在线程创建时 , 传入参数 , 将该参数转为 char* 字符串指针类型 , 将其打印出来 ;

4. 代码示例 :

/*
	定义线程中要执行的方法
	将该函数的指针作为线程创建方法 pthread_create 的第三个参数
	C++ 中规定线程执行函数的函数指针类型是 void *(PTW32_CDECL *start) (void *)
*/
void* pthread_function(void* args) {

	//延迟 100 ms 执行
	//_sleep(100);

	//指针类型转换 : 将 void* 转为 char*
	//	使用 static_cast 类型转换标识符
	char* hello = static_cast<char*>(args);

	//打印参数
	cout << "pthread_function 线程方法 执行 参数 : " << hello << endl;
	return 0;
}


III 线程标识符


1. 线程标识符 : pthread_t 类型 , 用于声明线程的 ID ;

2. 类型本质 : 该类型是一个结构体 ;

typedef struct {
    void * p;                   /* Pointer to actual object */
    unsigned int x;             /* Extra information - reuse count etc */
} ptw32_handle_t;

typedef ptw32_handle_t pthread_t;

3. 代码示例 : 声明线程标识符 , 下面的代码是在栈内存中声明线程标识符 , pthread_create 方法中需要传入指针 , 这里使用取地址符获取其指针 ;

	//线程标识符 , 这里需要传入指针 , 因此这里使用 & 取地址符获取其地址当做指针变量
	pthread_t pid;


IV 线程属性


1. 线程属性 : pthread_attr_t 表示线程属性类型 , 查看其类型声明 , 得到如下代码 , pthread_attr_t 类型是一个指针类型 ;

typedef struct pthread_attr_t_ * pthread_attr_t;

2. 线程属性声明 : 线程属性类型是一个指针 , 初始化时其值是随机值 , 是个野指针 , 这里将其设置为 0 ;

pthread_attr_t attribute = 0;

3. 线程属性的初始化和销毁 : 该线程属性需要先进行初始化和销毁;

  • ① 线程属性初始化 : 函数原型 int pthread_attr_init(pthread_attr_t *attr); ; 初始化线程属性时 , 对属性进行了默认配置 ;
	pthread_attr_init(&attribute);
  • ② 线程属性销毁 : 函数原型 int pthread_attr_destroy(pthread_attr_t *attr); ;
	//销毁线程属性
	pthread_attr_destroy(&attribute);

4. 二维指针参数 :

  • ① 参数说明 : 线程初始化和销毁方法传入 pthread_attr_t * 类型的参数 , pthread_attr_t 类型是指针 , pthread_attr_t *二维指针 ; 初始化时 , 肯定要创建一个有实际意义的线程属性结构体 , 将 attribute 二维指针指向线程属性结构体指针 ;
  • ② 指向指针的指针意义 : 在传递时可以 在函数内部 修改指针指向的地址 ;

5. 代码示例 :

	/*
		线程属性结构体变量
			该线程属性需要先进行初始化和销毁;
			线程属性初始化方法 : int pthread_attr_init(pthread_attr_t *attr);
			线程属性销毁方法 : int pthread_attr_destroy(pthread_attr_t *attr);

		线程属性类型定义 : typedef struct pthread_attr_t_ * pthread_attr_t;
			pthread_attr_t 其本质是一个指针 ; 
			pthread_attr_t attribute 声明后 , 该指针是野指针 , 需要将其设置为 0 ;
	*/
	pthread_attr_t attribute = 0;

	//初始化线程属性, 此处的参数是指针的指针 , 该指针指向 0 地址 ; 
	//	初始化时 , 肯定要创建一个有实际意义的线程属性结构体 , 将 attribute 二维指针 指向结构体指针
	//	指向指针的指针意义 : 在传递时可以在函数内部修改指针指向的地址 ; 
	//初始化线程属性时 , 对属性进行了默认配置 ;
	pthread_attr_init(&attribute);


V 线程属性 1 ( 分离线程 | 非分离线程 )


1. 线程的默认属性 : 线程创建后 , 默认是非分离线程 ;

2. 非分离线程 :

  • ① 特点 : 非分离线程允许在其它线程中 , 来等待另外线程执行完毕 ;
  • ② 表现 : 创建线程后 , 线程执行 , 如果调用 pthread_join 函数 , 其作用是等待 pthread_function 线程函数执行完毕 ;

3. 分离线程 : 不能被其它线程操作 , 如调用 pthread_join 函数 , 无法等待该分离线程执行完毕 ;

4. 非分离线程 与 分离线程 比较 :

  • ① 设置非分离线程属性 : 先执行完线程内容 , 等待线程执行完毕后 , 才执行 pthread_join 后的代码 ;
  • ② 设置分离线程属性 : pthread_join 等待线程执行完毕是无效的 , 主线程会继续向后执行 , 不会等待线程执行完毕

5. 分离线程不经常使用 : 一般情况下是不经常将线程设置为分离线程 , 如果设置了 , 那么该线程就无法进行控制 ;

6. 设置线程为分离线程代码示例 :

	pthread_attr_setdetachstate(&attribute, PTHREAD_CREATE_DETACHED);


VI 线程属性 2 ( 线程调度策略 )


该功能在 Android , Linux 上可以使用 , 在 Visual Studio 中暂时无法测试

1. 线程调度策略 : 线程是需要抢占 CPU 资源进行执行的 , 调度策略就是设置抢占 CPU 的策略 ;

2. SCHED_FIFO 策略 :

  • ① 调度机制 : 先创建的线程先执行 , CPU 一旦占用则一直占用 ;
  • ② CPU 资源释放时机 : 当有更高优先级的任务出现或线程执行完毕 , CPU 资源才会释放 ;
  • ③ 串行执行 : 如果两个线程都是 SCHED_FIFO 策略 , 并且优先级一样 , 那么两个线程一起执行的话 , 要先后执行 , 无法同时执行;

3. SCHED_RR 策略 :

  • ① 调度机制 : 时间片轮转 , 系统为不同的线程分配不同的时间段 , 指定的线程只有在指定的时间段内才能使用 CPU 资源 ;
  • ② 并行执行 : 如果两个线程都是 SCHED_RR 策略 , 并且优先级一样 , 那么两个线程一起执行的话 , 两个线程同时执行 ;

4. 调度策略设置方法 :

  • ① 函数原型 : int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
  • ② 参数 1 ( pthread_attr_t *attr ) : 线程属性对象 ;
  • ③ 参数 2 ( int policy ) : 调度策略 ;


VII 线程属性 3 ( 线程优先级设置 )


该功能在 Android , Linux 上可以使用 , 在 Visual Studio 中暂时无法测试

1. 线程优先级 : 优先级是一个数值 , 数值越大 , 优先级越高 , 系统在进行线程调度时 , 优先给优先级高的线程分配资源 , 优先级高的先执行 ;

2. 线程优先级类型 : 优先级是 sched_param 结构体变量 , 在 sched_param 结构体中只有一个成员sched_priority ;

		struct sched_param {
		  int sched_priority;
		};

3. 优先级取值范围 : 该范围与调度策略有关 , 可以获取该调度策略优先级的最大最小值 ;

  • ① 获取 SCHED_FIFO 策略的最大优先级 :
	//获取 SCHED_FIFO 策略的最大优先级
	int max_priority_of_fifo = sched_get_priority_max(SCHED_FIFO);
  • ② 获取 SCHED_FIFO 策略的最小优先级 :
	//获取 SCHED_FIFO 策略的最小优先级
	int min_priority_of_fifo = sched_get_priority_min(SCHED_FIFO);

4. 设置线程优先级代码示例 :

	//获取 SCHED_FIFO 策略的最大优先级
	int max_priority_of_fifo = sched_get_priority_max(SCHED_FIFO);
	//获取 SCHED_FIFO 策略的最小优先级
	int min_priority_of_fifo = sched_get_priority_min(SCHED_FIFO);

	//声明调度参数结构体
	sched_param param;
	//设置调度参数结构体的 sched_priority 成员
	param.sched_priority = max_priority_of_fifo;
	//设置线程优先级
	pthread_attr_setschedparam(&attribute, &param);


VIII 线程等待


1. 线程等待方法 :

  • ① 函数作用 : 等待线程结束 , 用于线程间同步操作 ;
  • ② 函数原型 : int pthread_join(pthread_t thread, void **retval); ;
  • ③ 参数 1 ( pthread_t thread ) : 线程标识符 , 要等待哪个线程结束 ;
  • ④ 参数 2 ( void **retval ) : 被等待线程的返回值 ;

2. 代码示例 :

	//pthread_join : 等待线程结束
	//	等线程执行完毕后 , 在执行下面的内容
	pthread_join(pid, 0);


IX 互斥锁


【C++ 语言】pthread_mutex_t 互斥锁



X 线程代码示例


1. 代码示例 :

// 005_Thread.cpp: 定义应用程序的入口点。
//

#include "005_Thread.h"
#include <pthread.h>

//引入队列的头文件
#include <queue>

using namespace std;

/*
	定义线程中要执行的方法
	将该函数的指针作为线程创建方法 pthread_create 的第三个参数
	C++ 中规定线程执行函数的函数指针类型是 void *(PTW32_CDECL *start) (void *)
*/
void* pthread_function(void* args) {

	//延迟 100 ms 执行
	//_sleep(100);

	//指针类型转换 : 将 void* 转为 char*
	//	使用 static_cast 类型转换标识符
	char* hello = static_cast<char*>(args);

	//打印参数
	cout << "pthread_function 线程方法 执行 参数 : " << hello << endl;
	return 0;
}

/*
	互斥锁 :
		声明 : 先声明互斥锁
		初始化 : 在进行初始化操作
		销毁 : 使用完毕后 , 要将该互斥锁销毁
*/
pthread_mutex_t mutex_t;

//声明一个队列变量
//	该变量是全局变量
//	该变量要在不同的线程中访问 , 用于展示线程同步
queue<int> que;

/*
	操作线程方法 : 参数和返回值都是 void* 类型

	互斥锁使用 : 多个线程对一个队列进行操作 , 
		需要使用互斥锁将该队列锁起来 , pthread_mutex_lock
		使用完毕后在进行解锁 , pthread_mutex_unlock

	该类型的锁与 Java 中的 synchronized 关键字一样 , 属于悲观锁
	其作用是通过 mutex 互斥锁 , 将上锁与解锁之间的代码进行同步 
*/
void* queue_thread_fun(void* args) {

	//先用互斥锁上锁
	pthread_mutex_lock(&mutex_t);

	if (!que.empty()) {
		//打印队列中的第一个元素
		printf("获取 que 队列第一个数据 : %d\n", que.front());

		//将队列首元素弹出
		que.pop();
	}
	else {
		printf("获取 que 队列为空\n");
	}

	//操作完毕后, 解锁
	pthread_mutex_unlock(&mutex_t);

	return 0;
}

/*
	如果 8 个线程同时读取队列中的信息 , 会出现程序崩溃
	在多线程环境下 , 对队列进 queue_thread 行操作 , queue_thread 是线程不安全的
	这里需要加锁 , 进行 线程同步的操作
*/
int main()
{

	//初始化互斥锁
	pthread_mutex_init(&mutex_t, 0);

	//向其中加入 5 个int数据
	for (size_t i = 0; i < 5; i++) {
		que.push(i);
		cout << "放入数据 : " << i << endl;
	}

	//创建多个线程操作 queue_thread 队列
	pthread_t pids[8];
	for (size_t i = 0; i < 8; i++) {
		//创建线程
		pthread_create(&pids[i], 0, queue_thread_fun, 0);
	}

	//销毁互斥锁
	pthread_mutex_destroy(&mutex_t);
	return 0;
}



int main2()
{
	cout << "Hello CMake。" << endl;

	// I. 测试 POSIX 线程方法
	pthread_self();


	// II 


	//线程标识符 , 这里需要传入指针 , 因此这里使用 & 取地址符获取其地址当做指针变量
	pthread_t pid;
	char* hello = "Hello Thread";

	/*
		线程属性结构体变量
			该线程属性需要先进行初始化和销毁;
			线程属性初始化方法 : int pthread_attr_init(pthread_attr_t *attr);
			线程属性销毁方法 : int pthread_attr_destroy(pthread_attr_t *attr);

		线程属性类型定义 : typedef struct pthread_attr_t_ * pthread_attr_t;
			pthread_attr_t 其本质是一个指针 ; 
			pthread_attr_t attribute 声明后 , 该指针是野指针 , 需要将其设置为 0 ;
	*/
	pthread_attr_t attribute = 0;

	//初始化线程属性, 此处的参数是指针的指针 , 该指针指向 0 地址 ; 
	//	初始化时 , 肯定要创建一个有实际意义的线程属性结构体 , 将 attribute 二维指针 指向结构体指针
	//	指向指针的指针意义 : 在传递时可以在函数内部修改指针指向的地址 ; 
	//初始化线程属性时 , 对属性进行了默认配置 ;
	pthread_attr_init(&attribute);


	//常用属性 1 : 
	//非分离线程 ;
	//	线程创建后 , 默认是非分离线程 ; 
	//	创建线程后 , 线程执行 , 如果调用 pthread_join 函数 , 其作用是等待 pthread_function 线程函数执行完毕 ; 
	//	非分离线程允许在其它线程中 , 来等待另外线程执行完毕 ; 
	//分离线程 : 
	//	不能被其它线程操作 , 如调用 pthread_join 函数 , 无法等待该分离线程执行完毕 ; 

	/*
		设置线程属性为 分离线程
			如果没有设置分离线程时 , 先执行完线程内容 , 等待线程执行完毕后 , 才执行 pthread_join 后的代码
			如果设置了分离线程属性 , pthread_join 等待线程执行完毕是无效的 , 主线程会继续向后执行 , 
				不会等待线程执行完毕 
			因此打印出的内容是 先打印 "线程执行完毕" , 然后才打印线程方法中的内容

		不经常使用 : 一般情况下是不经常将线程设置为分离线程 , 如果设置了 , 那么该线程就无法进行控制
	*/
	pthread_attr_setdetachstate(&attribute, PTHREAD_CREATE_DETACHED);

	/*
		常用属性 2 : 线程的调度策略
		该功能在 Android , Linux 上可以使用 , 在 Visual Studio 中暂时无法测试

		线程是需要抢占 CPU 资源进行执行的 , 调度策略就是设置抢占 CPU 的策略

		调度策略 : 
			SCHED_FIFO 策略 : 先创建的线程先执行 , CPU 一旦占用则一直占用 , 
				CPU 资源释放时机 : 当有更高优先级的任务出现或线程执行完毕 , CPU 资源才会释放
				串行执行 : 如果两个线程都是 SCHED_FIFO 策略 , 并且优先级一样 , 那么两个线程一起执行的话 , 
					要先后执行 , 无法同时执行; 
			SCHED_RR 策略 : 时间片轮转 , 系统为不同的线程分配不同的时间段 , 
				指定的线程只有在指定的时间段内才能使用 CPU 资源
				
				并行执行 : 如果两个线程都是 SCHED_RR 策略 , 并且优先级一样 , 
					那么两个线程一起执行的话 , 两个线程同时执行 

		调度策略设置方法 : 
			函数原型 : int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
			参数 1 ( pthread_attr_t *attr ) : 线程属性对象
			参数 2 ( int policy ) : 调度策略		
			
	*/

	/*
		常用属性 3 : 优先级设置
		该功能在 Android , Linux 上可以使用 , 在 Visual Studio 中暂时无法测试

		优先级是一个数值 , 数值越大 , 优先级越高 , 系统在进行线程调度时 , 
			优先给优先级高的线程分配资源 , 优先级高的先执行 ;   

		优先级是 sched_param 结构体变量 , 在 sched_param 结构体中只有一个成员sched_priority ; 
		struct sched_param {
		  int sched_priority;
		};

		优先级设置方法 : pthread_attr_setschedparam

		优先级取值范围 : 该范围与调度策略有关 , 可以获取该调度策略优先级的最大最小值
			下面有获取 SCHED_FIFO 的最高和最低优先级取值
	*/
	//获取 SCHED_FIFO 策略的最大优先级
	int max_priority_of_fifo = sched_get_priority_max(SCHED_FIFO);
	//获取 SCHED_FIFO 策略的最小优先级
	int min_priority_of_fifo = sched_get_priority_min(SCHED_FIFO);

	//声明调度参数结构体
	sched_param param;
	//设置调度参数结构体的 sched_priority 成员
	param.sched_priority = max_priority_of_fifo;
	//设置线程优先级
	pthread_attr_setschedparam(&attribute, &param);


	/*
		线程创建方法函数原型 : 
		int pthread_create(
			pthread_t *tidp, 
			const pthread_attr_t *attr, 
			(void*)(*start_rtn)(void*), 
			void *arg);
		
		该方法需要提供四个参数 ;
			参数 1 ( pthread_t *tidp ) :线程标识符指针 , 该指针指向线程标识符 ;
			参数 2 ( const pthread_attr_t *attr ) : 线程属性指针 ;
			参数 3 ( (void*)(*start_rtn)(void*) ) : 线程运行函数指针 , start_rtn 是一个函数指针 , 
				其参数和返回值类型是 void* 类型
			参数 4 ( void *arg ) : 参数 3 中的线程运行函数的参数 ;
		返回值 :
			线程创建成功 , 返回 0 ;
			线程创建失败 , 返回 错误代码 ;

		关于函数指针参数 : C++ 中函数指针类型是 void *(PTW32_CDECL *start) (void *) ,
			函数的参数类型是 void* 指针
			函数的返回值类型 void* 指针

		函数多参数方案 : 如果线程执行的函数有多个参数 , 可以使用结构体 , 类进行封装

		线程属性 : 创建线程时 , 给线程指定属性 pthread_attr_t 是结构体类型

	*/
	//函数指针 函数名 和 &函数名 都可以作为函数指针
	pthread_create(&pid , &attribute, pthread_function, hello);

	//pthread_join : 等待线程结束
	//	等线程执行完毕后 , 在执行下面的内容
	pthread_join(pid, 0);

	cout << " 线程执行完毕 " << endl;

	//销毁线程属性
	pthread_attr_destroy(&attribute);

	return 0;
}


2. 执行结果 :

放入数据 : 0
放入数据 : 1
放入数据 : 2
放入数据 : 3
放入数据 : 4
获取 que 队列第一个数据 : 0
获取 que 队列第一个数据 : 1
获取 que 队列第一个数据 : 2
获取 que 队列第一个数据 : 3
获取 que 队列第一个数据 : 4

D:\002_Project\006_Visual_Studio\005_Thread\out\build\x64-Debug\005_Thread\005_Thread.exe (进程 1852)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值