线程的概念:操作系统调度的最小单位。线程包含在进程中,是进程中实际运行的单位。一个进程中可以同时运行多个线程,每个线程可以执行不同的任务,这就是所谓的多线程。
内核:是操作系统最基本的部分,主要负责管理o系统资源
线程和进程的对比:
CPU调度和切换:线程的上下文切换比进程要快得多
线程更加廉价,启动速度更快,退出也快,对系统资源得冲击小
线程的终止:
线程函数返回:这是确保线程的所有资源被正确地清除的唯一办法。当线程函数返回时,如下情况将会发生:
在线程函数中创建的所有C++对象将通过它们的析构函数正确地撤销
操作系统将正确地释放线程的堆栈使用的内存;
系统将线程的退出代码设置为线程函数的返回值:
系统递减线程内核对象的引用计数
创建一个线程使用到的函数:
1.CreateThread函数:创建一个线程,函数原型
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
参数1:指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的句柄是否可以由子进程继承。 如果 lpThreadAttributes 为 NULL,则无法继承句柄
参数2:堆栈的初始大小(以字节为单位)。 系统将此值舍入到最近的页面。 如果此参数为零,新线程将使用可执行文件的默认大小 参数3:指向由线程执行的应用程序定义函数的指针。 此指针表示线程的起始地址 参数4:指向要传递给线程的变量的指针 参数5:控制线程创建的标志
返回值:会返回线程句柄
2.ExitThread函数:结束调用线程,函数原型
void ExitThread(
[in] DWORD dwExitCode
);
可以通过在线程中调用ExitThread函数,来强制终止自身线程的运行
该函数将终止自身线程的运行,,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源 (如C++对象)将不被正确地撤销
3.SuspendThread函数:挂起指定线程,函数原型
DWORD SuspendThread(
[in] HANDLE hThread
);
参数:对应线程的句柄
4.ResumeThread函数:递减线程的挂起计数。 当暂停计数递减为零时,将恢复线程的执行,就是恢复被挂起的线程,函数原型
DWORD ResumeThread(
[in] HANDLE hThread
);
参数:要重启的线程的句柄
返回值:如果函数成功,则返回值为线程的上一个挂起计数。如果函数失败,则返回值 (DWORD) -1。 要获得更多的错误信息,请调用 GetLastError
函数使用实例:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<windows.h>
using namespace std;
//回调函数
DWORD WINAPI ThreadProc(PVOID lp)
{
cout << "子线程" << endl;
Sleep(1000);
cout << "子线程2" << endl;
return 0;
}
int main()
{
//该函数会返回一个句柄
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, 0, CREATE_SUSPENDED, 0);//参数4设置了线程挂起,不会执行子线程
SuspendThread(hThread);//手动挂起
ResumeThread(hThread);//把子线程恢复
cout << "主线程" << endl;
Sleep(1500);
cout << "主线程2" << endl;
CloseHandle(hThread);
return 0;
}
5.InterlockedIncrement函数:将 (作为原子操作的指定 32 位变量的值增加一) 递增。若要对 64 位值进行操作,请使用 InterlockedIncrement64 函数。函数原型
LONG InterlockedIncrement(
[in, out] LONG volatile *Addend
);
参数:变量的地址
返回值:返回函数的递增值
临界区:临界区是一段连续的代码区域,它要求在执行前获得对某些共享数据的独占的访问权。如果一个进程中的所有线程中访问这些共享数据的代码都放在临界区中,就能够实现对该共享数据的同步访问。临界区只能用于同步单个进程中的线程
结构体 CRITICAL_SECTION 是创建临界区的对象,
6. InitializeCriticalSection函数:初始化关键节对象,也就是初始化临近区对象,函数原型
void InitializeCriticalSection(
[out] LPCRITICAL_SECTION lpCriticalSection
);
参数:指向关键节对象的指针,也就是临界区的地址
返回值:没有返回值
7. EnterCriticalSection函数:等待指定关键部分对象的所有权,等待临近区,函数原型
void EnterCriticalSection(
[in, out] LPCRITICAL_SECTION lpCriticalSection
);
参数:临界区地址
返回值:没有返回值
8. LeaveCriticalSection函数:释放指定关键节对象的所有权,释放临界区所有权,函数原型
void LeaveCriticalSection(
[in, out] LPCRITICAL_SECTION lpCriticalSection
);
参数:临界区地址
返回值:没有返回值
内核对象:常用的内核对象有互斥变量、信号量和事件,其他的还包括文件、控制台输入、文件变化通知、可等待的计时器
每一个内核对象在任何时候都处于两种状态之一: 信号态 (signaled) 和无信号态(nonsignaled)
互斥变量:通俗讲就是锁,类似于临界区,但它能够同步多个进程间的数据访问
9. CreateMutex函数:创建或打开命名或未命名的互斥对象,就是创建互斥变量,函数原型
HANDLE CreateMutexW(
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
[in] BOOL bInitialOwner,
[in, optional] LPCWSTR lpName
);
参数1:指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄
参数2:如果此值为 TRUE ,并且调用方创建了互斥体,则调用线程获取互斥体对象的初始所有权。 否则,调用线程不会获取互斥体的所有权
参数3:互斥体对象的名称
返回值:如果函数成功,则返回值是新创建的互斥体对象的句柄,否则返回NULL
10. WaitForSingleObject函数:等待指定对象处于信号状态或超时间隔已过,也就是等待有信号的互斥变量或者是事件,获取对象的所有权,函数原型
DWORD WaitForSingleObject(
[in] HANDLE hHandle,
[in] DWORD dwMilliseconds
);
参数1:对象的句柄
参数2:超时间隔,为 INFINITE,则仅当发出对象信号时,该函数才会返回
返回值:如果函数成功,则返回值指示导致函数返回的事件
11. ReleaseMutex函数:释放指定互斥对象的所有权,需要和WaitForSingleObject函数联合使用,函数原型
BOOL ReleaseMutex(
[in] HANDLE hMutex
);
参数1:互斥对象的句柄
返回值:如果该函数成功,则返回值为非零值,如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError
12. SetEvent函数:将指定的事件对象设置为非对齐状态,释放事件所有权,函数原型
BOOL ResetEvent(
[in] HANDLE hEvent
);
参数:事件对象的句柄
返回值:如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError
函数使用实例:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<windows.h>
using namespace std;
//原子锁
unsigned int g;
//临界区
CRITICAL_SECTION g_sec;
//线程锁
HANDLE hmutex;
//事件
HANDLE hEvent;
//回调函数
DWORD WINAPI ThreadProc(PVOID lp)
{
for (int i = 0; i < 1000000; i++)
{
//InterlockedIncrement(&g);//以原子方式加1,相当于给变量加锁
进入临界区
//EnterCriticalSection(&g_sec);
//g++;
//LeaveCriticalSection(&g_sec);
//WaitForSingleObject(hmutex, INFINITE);//等待锁变成有信号
//g++;
//ReleaseMutex(hmutex);//把锁置为无信号
WaitForSingleObject(hEvent, INFINITE);//等待有信号的事件
g++;
SetEvent(hEvent);//把事件置为无信号
}
return 0;
}
int main()
{
//初始化临界区对象
InitializeCriticalSection(&g_sec);
//创建锁
hmutex = CreateMutex(NULL, FALSE, NULL);
//创建事件对象
hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, 0);
for (int j = 0; j < 1000000; j++)
{
//InterlockedIncrement(&g);
进入临界区
//EnterCriticalSection(&g_sec);
//g++;
//LeaveCriticalSection(&g_sec);
//WaitForSingleObject(hmutex, INFINITE);//等待锁变成有信号
//g++;
//ReleaseMutex(hmutex);//把锁置为无信号
WaitForSingleObject(hEvent, INFINITE);//等待锁变成有信号
g++;
SetEvent(hEvent);//把锁置为无信号
}
WaitForSingleObject(hThread, INFINITE);//等待线程
printf("%d", g);
CloseHandle(hThread);
CloseHandle(hmutex);
DeleteCriticalSection(&g_sec);//释放临界区
return 0;
}
线程间的通信
使用全局变量进行通信
参数传递方式
消息传递方式
进程概念:
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元
创建状态:进程在创建时需要申请一个空白PCB (进程控制块),向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
就绪状态: 进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
执行状态: 进程处于就绪状态被调度后,进程进入执行状态
阻寒状态:正在执行的进程由于某些事件 (I/O请求,申请缓存区失败) 而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用
终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行
进程的同步与互斥:
同步:为了完成某个任务而建立的两个或多个进程,这些进程为了需要在某些位置上协调它们的工作次序而等待传递信息所产生的制约关系。进程间的制约关系就是源于它们之间的相互合作
互斥:当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界区资源的进程退出临界区以后才允许进入临界区,访问资源。注意,这里的一个并非卡死就一次只能一个进程访问临界资源。当信号量个数大于1的时候,一次可以有多个进程同时访问临界资源。前文的互斥定义只是最原始的定义
同步准则:
空闲让进:临界区(每个进程中访问临界资源的那段代码称为临界区) 空闲时,应该允许某个进程立即进入临界区访问临界资源
忙则等待:当临界区进程访问数达到访问上限时,其他试图进入临界区的进程应该等待
有限等待:应该避免进程--直等待永远进入不了临界D的情况
让权等待:“权”即CPU使用权,当进程不能进入临界区的时候,应该放弃使用CPU,进入等待队列,而不是"忙等待”
进程中使用到的函数:
CreateProcess函数:创建新的进程及其主线程,函数原型
BOOL CreateProcessW(
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
参数1:要执行的模块的名称
参数2:要执行的命令行
参数3:指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的句柄是否可由子进程继承到新进程对象。 如果 lpProcessAttributes 为 NULL,则无法继承句柄
参数4:指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定新线程对象的返回句柄是否可以由子进程继承。 如果 lpThreadAttributes 为 NULL,则无法继承句柄
参数5:如果此参数为 TRUE,则调用过程中的每个可继承句柄都由新进程继承。 如果参数为 FALSE,则不会继承句柄
参数6:控制优先级类和创建进程的标志
参数7:指向新进程的环境块的指针。 如果此参数为 NULL,则新进程使用调用进程的环境
参数8:进程当前目录的完整路径。 字符串还可以指定 UNC 路径
参数9:指向 STARTUPINFO 或 STARTUPINFOEX 结构的指针
参数10:指向接收有关新进程的标识信息的 PROCESS_INFORMATION 结构的指针
代码实例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
int main()
{
TCHAR commandline[] = L"C:\\Users\\君莫笑\\AppData\\Local\\Mozilla Firefox\\firefox.exe";
_STARTUPINFOW startinfo = { sizeof(_STARTUPINFOW) };//
_PROCESS_INFORMATION processInfo;//参数10,进程信息的结构体
//创建进程
bool ret = CreateProcess(
NULL,//不指定可执行文件的文件名
commandline,//命令行参数
NULL,//默认进程安全性
NULL,//默认线程安全性
FALSE,//指定当前进程的句柄是否被子进程继承
0,//指定附加的、用来控制优先类和进程的创建的标志
NULL, //使用当前进程的环境变量
NULL,//使用当前进程的驱动和目录
&startinfo,//指定一个用于决定新进程的主窗体如何显示的STARTUPINEO的结构体
&processInfo//进程信息结构体
);
if (!ret)
{
printf("创建进程失败\n");
return 0;
}
else
{
printf("创建进程的ID:%d\n", processInfo.dwProcessId);
printf("创建的主线程ID:%d\n", processInfo.dwThreadId);
WaitForSingleObject(processInfo.hProcess, INFINITE);//等待进程句柄
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
return 0;
}
进程间的通信:
四种方式:
无名管道 (pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
命名管道 (named pipe)I: 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
消息队列 (message queue): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
互斥量
共享内存(shared memory): 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信
使用共享内存通信使用到的函数:
CreateFileMapping函数:为指定文件创建或打开命名或未命名的文件映射对象,也就是创建一个映射内存的句柄,函数原型
HANDLE CreateFileMappingW(
[in] HANDLE hFile,
[in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
[in] DWORD flProtect,
[in] DWORD dwMaximumSizeHigh,
[in] DWORD dwMaximumSizeLow,
[in, optional] LPCWSTR lpName
);
参数1:要从中创建文件映射对象的文件的句柄
参数2:指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定返回的句柄是否可以由子进程继承
参数3:指定文件映射对象的页面保护,设置权限
参数4:文件映射对象的最大大小的高阶 DWORD
参数5:文件映射对象的名称
返回值:如果函数成功,则返回值是新创建的文件映射对象的句柄,如果函数失败,则返回值为 NULL
MapViewOfFile函数:将文件映射的视图映射到调用进程的地址空间,创建存储内容的缓冲空间,函数原型
LPVOID MapViewOfFile(
[in] HANDLE hFileMappingObject,
[in] DWORD dwDesiredAccess,
[in] DWORD dwFileOffsetHigh,
[in] DWORD dwFileOffsetLow,
[in] SIZE_T dwNumberOfBytesToMap
);
参数1:文件映射对象的句柄
参数2:对文件映射对象的访问类型,用于确定页面的页面保护
参数3:视图开始位置的文件偏移量的高顺序 DWORD
参数4:要开始视图的文件偏移量低序 DWORD
参数5:要映射到视图的文件映射的字节数
返回值:如果函数成功,则返回值为映射视图的起始地址。如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError
UnmapViewOfFile函数:从调用进程的地址空间取消映射文件的映射视图,关闭由MapViewOfFile创建出来的缓存空间,函数原型
BOOL UnmapViewOfFile(
[in] LPCVOID lpBaseAddress
);
参数:指向要取消映射的文件的映射视图基址的指针
返回值:如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError
函数使用实例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<windows.h>
int main()
{
//创建共享文件句柄
HANDLE hmap = CreateFileMapping(
NULL,//物理文件句柄
NULL,//默认映射文件的安全级别
PAGE_READWRITE,//访问权限
0,//高位
128,//低位
TEXT("fileMap")//共享内存名
);
//创建映射区
char* buf = (char*)MapViewOfFile(
hmap,
FILE_MAP_ALL_ACCESS,
0,
0,
128
);
gets_s(buf, 128);
while (1);
UnmapViewOfFile(buf);//关闭缓冲区
CloseHandle(hmap);//关闭句柄
return 0;
}