C/C++ Muti-Thread多线程编程学习(之)线程Thread | 创建、运行、结束
前言
多线程(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 | 创建、运行、结束的更多相关文章
- 多线程编程学习笔记——async和await(一)
接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...
- 多线程编程学习笔记——async和await(二)
接上文 多线程编程学习笔记——async和await(一) 三. 对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...
- 多线程编程学习笔记——async和await(三)
接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五. 处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...
- 多线程编程学习笔记——使用异步IO(一)
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——使用异步IO
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
- 多线程编程学习笔记——异步调用WCF服务
接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...
- 学习笔记——线程 Thread
Thread是.net1.0 1.1时出现的 主要了解线程等待.回调.前后台线程区别 1.实例: //定义:public delegate void ThreadStart(); ThreadStar ...
- [Java123] JDBC and Multi-Threading 多线程编程学习笔记
项目实际需求:DB交互使用多线程实现 多线程编程基础:1.5 :( (假设总分10) 计划一个半月从头学习梳理Java多线程编程基础以及Oracle数据库交互相关的多线程实现 学习如何通过代码去验证 ...
随机推荐
- js实现复制|剪切指定内容到粘贴板--clipboard
这是著名开源项目 clipboard.js 的 README.md,里面讲解的更加详细,有兴趣的同学可以了解一下.项目地址:https://github.com/zenorocha/clipboard ...
- Python_day01——变量
变量 1.声明变量 name="钱成龙" 变量定义的规则: 变量名只能是 字母.数字或下划线的任意组合 变量名的第一个字符不能是数字 关键字不能声明为变量名 2.变量类型 整 ...
- linux浏览器,邮件客户端,输入法,双屏设置,应用软件,gnome-screenshot/scrot -s截图,office
搜狗输入法linux版:http://pinyin.sogou.com/linux/help.php win/linux同时支持比较好用的浏览器:maxthon,firefox,maxthon,ope ...
- 使用ansible远程管理集群
使用ansible远程执行命令 1.ansible简介 ansible的官方定义:"Ansible is Simple IT Automation"--简单的自动化IT工具.这个工 ...
- HTML中margin和padding的区别
我们以DIV为一个盒子例子,既然和显示生活中的盒子一样,那我们想一下,生活中的盒子 内部是不是空的好用来存放东西,而里面存放东西的区域我们给他起个名字叫“content(内 容)”,而盒子的纸壁给他起 ...
- 19、Linux命令对服务器内存进行监控
国际惯例,我们要知道什么是服务器的内存,内存有哪些作用.这里就不做过多介绍,Linux性能监控需要我们对底层要有一定的理解.下面我将会列出我常用的监控内存的工具. vmstat vmstat显示关于进 ...
- SQL中Truncate语法
转自:http://www.studyofnet.com/news/555.html 本文导读:删除表中的数据的方法有delete,truncate, 其中TRUNCATE TABLE用于删除表中的所 ...
- Apache Solr远程命令执行复现
环境 /vulhub/solr/CVE-2019-0193/ 创建一个集合 docker-compose exec solr bash bin/solr create_core -c test -d ...
- playbackRate控制音频播放倍速
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- B. Light bulbs(2019 ICPC上海站)
There are NN light bulbs indexed from 00 to N-1N−1. Initially, all of them are off. A FLIP operation ...