C/C++ Muti-Thread多线程编程学习(之)线程Thread | 创建、运行、结束

2023-10-18 00:08

本文主要是介绍C/C++ Muti-Thread多线程编程学习(之)线程Thread | 创建、运行、结束,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 前言
    • 线程 Thread
      • 创建线程
        • CreateThread
        • _beginthread
        • _beginthreadex
        • pthread_create
      • 线程运行
      • 结束线程

前言


  多线程(Multi-Thread),是指从软件或者硬件上实现多个线程并发执行的技术。无论你是软件开发工程师(Software Engineer),还是算法工程师(Algorithm Engineer),当遇到性能优化需求时,多线程技术是不可绕开的一项。

  现代处理器是多核处理器架构,单处理器有多个独立运算核,可以并发运算,这个特性使得总任务可以被划分为多个独立的子任务同时运行,大大提高运行效率。本系列将对c/c++多线程编程中运用最广泛的概念做一个入门介绍,希望能对读者学习多线程编程有所帮助。

  本文要介绍的是最基础的那个概念:线程Thread

线程 Thread


  我们先看看Wiki对Thread的介绍:

  In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by a scheduler.
  Multiple threads can exist within one process, executing concurrently and sharing resources such as memory, while different processes do not share these resources.

  在计算机科学中,执行线程是能被调度器独立管理的程序化指令中的最小序列。
  多个线程可存在与一个进程中,同时执行且共享资源(如内存资源),而不同的进程则不能共享这些资源。

  从以上介绍中,我们提炼出两个关键点:线程之间彼此独立、共享资源。这表明线程不仅可以独立并发,而且可以通信协作。也就是说,多线程不是各自为战,而是同舟共济。

  在多线程编程中,线程是最基本的概念。面对一个任务并行的多线程问题,我们首先要将总任务分为多个子任务,一个子任务就可以放入一个线程中执行,这时我们需要编写多个线程,每个线程负责独立的任务;而面对一个数据并行的多线程问题,我们只有一个任务,是将数据分为多个独立的子数据,每个数据都执行同样的任务,这时我们只需要编写一个线程,通过管理线程的输入来完成多个子数据的并行任务。所以我们把线程想象成任务即可,这个任务要完成多少工作无关紧要,只要多个任务是独立的,或者同一个任务有多个独立的输入。

创建线程

CreateThread

  在C语言中,如何创建一个线程?在Windows系统中,我们可以使用CreateThread函数:

HANDLE WINAPI CreateThread(_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,		_In_ SIZE_T dwStackSize,									_In_ LPTHREAD_START_ROUTINE lpStartAddress,				_In_opt_ __drv_aliasesMem LPVOID lpParameter,			_In_ DWORD dwCreationFlags,_Out_opt_ LPDWORD lpThreadId
);

  参数说明:
  lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
  dwStackSize:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
  lpStartAddress:指向线程函数的指针,形式:函数名,函数名称没有限制,但是必须以下列形式声明:

DWORD WINAPI 函数名 (LPVOID lpParam) 

  格式不正确将无法调用成功。
  lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
  dwCreationFlags :线程标志,可取值如下
  (1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
  (2)0:表示创建后立即激活。
  (3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
  lpThreadId:保存新线程的id。若不想返回线程ID,设置值为NULL。
  返回值:函数成功,返回线程句柄;函数失败返回false。

  代码示例:

#include "stdafx.h"
#include "windows.h"//线程函数定义
DWORD WINAPI ThreadFunc(LPVOID lpParam)
{Sleep(1000);return 0;
}
int main()
{DWORD dwThreadId = 0;	//线程ID//创建线程HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &dwThreadId);return 0;
}
_beginthread

  实际上,CreateThread函数并不被推荐使用,当软件需要调用CRT库时,使用CreateThread因为没有对子线程为CRT库分配堆,会导致内存错误而崩溃。

  我们可以使用另一个创建线程函数:_beginthread():

uintptr_t _beginthread(
void( *start_address )( void * ),
unsigned stack_size,
void *arglist
);

  参数说明:
  start_address:新线程的起始地址 ,指向新线程调用的函数的起始地址,为函数名。函数的声明方式很简单:

void 函数名(LPVOID lpParam)

  stack_size:新线程的堆栈大小,一般为0
  arglist:传递给线程的参数列表,无参数时为NULL

  _beginthread要比CreateThread安全,它是对CreateThread函数的封装,并针对CreateThread在调用CRT库时的内存泄漏问题进行了处理,所以比CreateThread更加安全。_beginthrad对应的关闭线程函数为为_endthread。

  代码示例:

#include "stdafx.h"
#include "windows.h"
#include "process.h"//线程函数定义
void ThreadFunc(void* param)
{Sleep(1000);
}int main()
{//创建线程_beginthread(ThreadFunc, 0, NULL);return 0;
}
_beginthreadex

  _beginthread函数非常简单,易上手,但参数太少也有缺点,和CreateThread比起来似乎可控制性不强?别慌,我们还有另一个函数_beginthreadex:

unsigned long _beginthreadex(void *security,unsigned stack_size,  unsigned(_stdcall *start_address)(void *), void *argilist, unsigned initflag,  unsigned *threaddr 
);

  参数说明:
  security:安全属性, 为NULL时表示默认安全性
  stack_size:线程的堆栈大小, 一般默认为0
  start_address:所要启动的线程函数
  argilist:线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
  flag:新线程的初始状态,0表示立即执行,CREATE_SUSPEND表示创建之后挂起
  threaddr:线程ID地址
  返回值:成功返回新线程句柄, 失败返回0

  可以看到,_beginthreadex就像_beginthread的扩展体,增加了三个参数,是可控制性更好一些。两个函数的不同点如下:
  (1)_beginthreadex()比_beginthread()多3个参数:intiflag,security和threadaddr。
  (2)线程函数的声明方式不同。_beginthreadex()的线程函数必须使用_stdcall修饰符,且必须返回一个unsigned int型的退出码。

unsigned __stdcall 函数名(void* param)

  (3)_beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1。这一点是在检查返回结果时必须注意的。
  (4)如果是调用_beginthread创建线程,则相应地调用_endthread结束线程时,系统会自动关闭线程句柄;而调用_beginthreadx创建线程,相应地调用_endthreadx结束线程时,系统不能自动关闭线程句柄。因此调用_beginthreadx创建线程还需程序员自己关闭线程句柄,以清除线程的地址空间。

  _beginthread和_beginthreadex都包含于头文件 process.h 中。
  代码示例:

#include "stdafx.h"
#include "windows.h"
#include "process.h"//线程函数定义
unsigned __stdcall ThreadFunc(void* param)
{Sleep(1000);return 0;
}int main()
{unsigned uiThreadId = 0;//创建线程HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uiThreadId);return 0;
}
pthread_create

  pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。包含于头文件pthread.h中。

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg
);

  参数说明:
  tidp:第一个参数为指向线程标识符的指针。
  attr:用来设置线程属性。
  start_rtn:线程运行函数的起始地址。
  arg:运行函数的参数。
  若线程创建成功,则返回0。若线程创建失败,则返回出错编号,并且*thread中的内容是未定义的。
  返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

线程运行

  一般来说,线程创建之后,会自动运行,如使用_beginthread创建的线程,但是当设置了线程启动状态时,可以控制线程的启动时刻,使用CreateThread和_beginthreadex创建线程才能完成这个操作,将creation flag设置为CREATE_SUSPENDED,这时线程创建后将被挂起,直到调用ResumeThread函数激活线程。
  代码示例:

#include "stdafx.h"
#include "windows.h"
#include "process.h"//线程函数定义
unsigned __stdcall ThreadFunc(void* param)
{Sleep(1000);return 0;
}int main()
{unsigned uiThreadId = 0;	//线程ID//创建线程并挂起HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, CREATE_SUSPENDED, &uiThreadId);//激活线程ResumeThread(hThread);return 0;
}

  在线程运行过程中,我们往往要等待线程运行结束再往下执行。可以使用WaitForSingleObject来执行这一工作。

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);

  参数说明:
  hHandle:线程句柄
  dwMilliseconds:等待时间,单位为milliseconds(毫秒)。如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
  代码示例:

#include "stdafx.h"
#include "windows.h"
#include "process.h"//线程函数定义
unsigned __stdcall ThreadFunc(void* param)
{Sleep(1000);return 0;
}int main()
{unsigned uiThreadId = 0;	//线程ID//创建线程并挂起HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, CREATE_SUSPENDED, &uiThreadId);//激活线程ResumeThread(hThread);WaitForSingleObject(hThread, INFINITE);return 0;
}

结束线程

  每个创建线程函数,都会有着对应的结束线程函数,CreateThread对应ExitThread,_beginthread对应_endthread,_beginthreadex对应_endthreadex,可以在必须强制结束线程的时候调用它们,但是最好避免这样做,因为中途强制结束,会导致不可控制的资源泄漏。一般而言,等待线程函数自行return是最好的方式,这时系统会自动调用线程终止函数,并释放所有资源。

这篇关于C/C++ Muti-Thread多线程编程学习(之)线程Thread | 创建、运行、结束的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/228854

相关文章

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的