线程
线程概念
1.时间上的概念:是附属在进程上的执行体当前正在运行的具体的代码 。
创建线程:使用CreateThread()函数
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全描述符
[in] SIZE_T dwStackSize,//堆栈的初始大小(不写系统会给出默认的 1MB大小
[in] LPTHREAD_START_ROUTINE lpStartAddress,//新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
[in, optional] __drv_aliasesMem LPVOID lpParameter,//指向要传递给线程的参数的指针
[in] DWORD dwCreationFlags,//创建线程的标识,两个选项(0:立即进入执行的状态;CREATE_SUSPENDED:宏,使用额外api(ResumeThread)改变唤起的状态,激活当前线程
[out, optional] LPDWORD lpThreadId//返回刚创建的线程id,传入NULL表示不需要返回该线程ID号
);
创建线程
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(500);
printf("+++++++++%d\n", i);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread;
int n = 10; //循环次数
hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL); //通过第四个参传参,强制类型转换
CloseHandle(hThread);
for (int i = 0; i < 100; i++)
{
Sleep(500);
printf("---------%d\n", i);
}
return 0;
}
运行发现两段代码同时执行的效果并不是有规律的各执行一 下, 而是较为混乱的各自执行各自的。
线程API
挂起,恢复线程::SuspendThread(), ResumeThread()
DWORD WINAPI SuspendThread(
_In_HANDLE hThread //进程句柄
);
DWORD WINAPI ResumeThread(
_In_ HANDLE hThread //进程句柄
);
应用:
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter)
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("+++++++++%d\n", i);
}
return 0;
}
//如何挂起、恢复线程 Sleep() Suspend() 和 Resume() 的使用
int main(int argc, char* argv[])
{
HANDLE hThread;
int n = 100; //循环次数
hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL); //通过第四个参传参,强制类型转换
Sleep(5000);
SuspendThread(hThread);
SuspendThread(hThread);
Sleep(5000);
ResumeThread(hThread); //几个挂起就需要几个回复
ResumeThread(hThread);
getchar();
CloseHandle(hThread);
return 0;
}
等待线程
等待一个线程
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
hHandle[in]:
对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds[in]:
定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回
应用:
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter)
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("+++++++++%d\n", i);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread;
int n = 100; //循环次数
hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL); //通过第四个参传参,强制类型转换
WaitForSingleObject(hThread,INFINITE);
printf("Execution completed--------------------");
getchar();
CloseHandle(hThread);
return 0;
}
等待多个线程
DWORD WaitForMultipleObjects(
DWORD nCount, //数组中的对象句柄数
const HANDLE* lpHandles, //[in]一组对象句柄。该数组可以包含不同类型对象的句柄。
BOOL bWaitAll, //[in] 如果此参数为TRUE,则在lpHandles数组中的所有对象的状态发出信号时,该函数返回。如果为FALSE,则当任何一个对象的状态设置为信号时,该函数返回。在后一种情况下,返回值表示其状态导致函数返回的对象。
DWORD dwMilliseconds //INFINITE
);
应用:
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter)
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("+++++++++%d\n", i);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE arrThread[2];
int n = 100;
//循环次数
arrThread[0] = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL);//第四个参传参,强制类型转换
arrThread[1] = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL);
WaitForMultipleObjects(2,arrThread,TRUE,INFINITE);
//等待两个线程进行完毕
printf("Execution completed--------------------");
getchar();
CloseHandle(arrThread);
return 0;
}
获取已终止线程的返回值:GetExitCodeThread()
用于获取一个已中止线程的退出代码
BOOL GetExitCodeThread(
HANDLE hThread, //in,获取退出代码的一个线程的句柄
LPDWORD lpExitCode //out,存储线程结束代码,也就是线程的返回值
);
示例:
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter) //thread1
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("+++++++++%d\n", i);
}
return 1;
}
DWORD WINAPI ThreadProc2(_In_ LPVOID lpParameter) //thred2
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("---------%d\n", i);
}
return 2;
}
int main(int argc, char* argv[])
{
HANDLE arrThread[2];
int n = 100; //循环次数
arrThread[0] = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL);
arrThread[1] = CreateThread(NULL, 0, ThreadProc2, (LPVOID)&n, 0, NULL);
DWORD dwresult_1;
DWORD dwresult_2;
GetExitCodeThread(arrThread[0],&dwresult_1);
GetExitCodeThread(arrThread[1],&dwresult_2);
//获取两个线程的返回值
WaitForMultipleObjects(2,arrThread,TRUE,INFINITE);
//等待两个线程进行完毕
printf("Execution completed--------------------");
printf("result_1:%d\nresult_2:%d",dwresult_1,dwresult_2);
getchar();
CloseHandle(arrThread[0]);
CloseHandle(arrThread[1]);
return 0;
}
运行
获取线程上下文
因为单核cpu需要同时运行多个线程
所以运行完a线程时需要记录下当前a线程执行到哪里了(线程上下文)
就需要记录下来a线程此时各个寄存器中的值,以便切换回来能够从截断的位置继续跑
所以每一个线程都有一个用来储存这些值的结构体:
CONTEXT
转到定义可以看到好多寄存器名称
用到的函数
BOOL GetThreadContext(
[in] HANDLE hThread,
[in, out] LPCONTEXT lpContext //指向接收指定线程的相应上下文的CONTEXT结构的指针
示例:
#include"stdafx.h"
#include<stdio.h>
#include <windows.h>
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter) //thread1
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("+++++++++%d\n", i);
}
return 1;
}
DWORD WINAPI ThreadProc2(_In_ LPVOID lpParameter) //thred2
{
int *p = (int *)lpParameter;
for (int i = 0; i < *p; i++)
{
Sleep(25);
printf("---------%d\n", i);
}
return 2;
}
int main(int argc, char* argv[])
{
HANDLE arrThread[2];
int n = 100; //循环次数
arrThread[0] = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL); //通过第四个参数传参,强制类型转换
arrThread[1] = CreateThread(NULL, 0, ThreadProc2, (LPVOID)&n, 0, NULL);
SuspendThread(arrThread[0]);
CONTEXT context; //定义
context.ContextFlags = CONTEXT_INTEGER; //赋值
GetThreadContext(arrThread[0],&context); //调用函数实现将arrThread[0]的信息存入context结构体中
printf("EAX: %x\nECX: %x",context.Eax,context.Ecx);
ResumeThread(arrThread[0]);
CloseHandle(arrThread[0]);
CloseHandle(arrThread[1]);
return 0;
}
控制线程的方式
临界区(应用层)
线程安全问题:
多线程访问局部变量产生:
- 有全局变量
- 对全局变量不是只读
示例:
#include <stdio.h>
#include <windows.h>
int g_dwTickets = 10; //全局变量
DWORD WINAPI ThreadProc1(LPVOID IpParameter)
{
while(g_dwTickets > 0 ) //判断是否还有余票
{
printf("还有:%d张票 \n",g_dwTickets);
g_dwTickets--;
printf(" 卖掉一张,还有%d张\n",g_dwTickets);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID IpParameter)
{
while(g_dwTickets > 0 ) //判断是否还有余票
{
printf("还有:%d张票 \n",g_dwTickets);
g_dwTickets--;
printf(" 卖掉一张,还有%d张\n",g_dwTickets);
}
return 0;
}
int main()
{
HANDLE arrhThread[2];
arrhThread[0] = CreateThread(
NULL,
0,
ThreadProc1,
NULL,
0,
NULL
);
arrhThread[1] = CreateThread(
NULL,
0,
ThreadProc2,
NULL,
0,
NULL
);
//等线程结束了
WaitForMultipleObjects(2,arrhThread,TRUE,INFINITE);
//线程执行完了
CloseHandle(arrhThread[0]);
CloseHandle(arrhThread[1]);
getchar();
return 0;
}
输出:
运行,看到不合理的数据。
解决这个问题需要让全局变量在被一个线程使用时不能被其它线程使用
解决
临界资源、临界区
临界资源一次只允许一个线程使用的全局变量
临界区是访问临界资源的区域
用到的API
CRITICAL_SECTION cs; //创建全局变量的API
EnterCriticalSection(&cs); //进入临界区的API
LeaveCriticalSection(&cs); //离开临界区API
InitializeCriticalSection(&cs); //初始化全局变量
通过线程锁解决
#include <stdio.h>
#include <Windows.h>
int number = 10;
CRITICAL_SECTION cs; //创建全局变量的API
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
EnterCriticalSection(&cs); //进入临界区的API
while (number > 0)
{
printf("还有:%d张票\n", number);
number--;
printf(" 卖掉一张,还有%d张\n", number);
}
LeaveCriticalSection(&cs); //离开临界区API
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
EnterCriticalSection(&cs);
while (number > 0)
{
printf("还有:%d张票\n", number);
number--;
printf(" 卖掉一张,还有%d张\n", number);
}
LeaveCriticalSection(&cs);
return 0;
}
int main()
{
HANDLE arrhtherd[2];
InitializeCriticalSection(&cs); //初始化全局变量
arrhtherd[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
arrhtherd[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
WaitForMultipleObjects(2, arrhtherd, TRUE, INFINITE);
CloseHandle(arrhtherd[0]);
CloseHandle(arrhtherd[1]);
getchar();
}
在这里插入图片描述
succeed!
互斥体(内核层)
能够放在内核中的一个令牌(参考上一部分)
常用于两个 进程 之间线程控制
示例:
进程1:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char* argv[])
{
//创建一个互斥体
HANDLE g_hMutex = CreateMutex(NULL, FALSE,"name");
//获取令牌
WaitForSingleObject(g_hMutex, INFINITE);
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("A进程的X线程:%d \n", i);
}
//释放令牌
ReleaseMutex(g_hMutex);
return 0;
}
进程2:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char* argv[])
{
//创建一个互斥体
HANDLE g_hMutex = CreateMutex(NULL, FALSE,"name");
//获取令牌
WaitForSingleObject(g_hMutex, INFINITE);
for (int i = 0; i < 10; i++)
{
Sleep(1000);
printf("B进程的Y线程:%d \n", i);
}
//释放令牌
ReleaseMutex(g_hMutex);
return 0;
}
运行时后运行的进程会在先运行的进程结束后才开始运行
事件
HANDLECreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性
BOOL bManualReset,// 复位方式,TRUE为通知类型,FALSE为互斥类型。
BOOL bInitialState,// 初始状态,FALSE为无信号,TURE为有信号
LPCTSTR lpName // 对象名称
);
bManualReset:指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个线程等待到事件信号后系统会自动将事件状态复原为无信号状态。
示例:
#include <stdio.h>
#include <Windows.h>
HANDLE g_hEvent;
DWORD WINAPI ThreadProc_1(LPVOID IpParameter)
{
TCHAR szBuffer[10] = { 0 };
//当事件变成通知时
WaitForSingleObject(g_hEvent, INFINITE);
printf("Thread_1 has already executed\n");
getchar();
return 0;
}
DWORD WINAPI ThreadProc_2(LPVOID IpParameter)
{
TCHAR szBuffer[10] = { 0 };
//SetEvent(g_hEvent); //若为互斥类型,标志需要手动更改,于是就要在被阻塞的地方加上SetEvent(g_hEvent);
//若为FALSE则不需要
WaitForSingleObject(g_hEvent, INFINITE);
printf("Thread_2 has already executed\n");
getchar();
return 0;
}
int main()
{
g_hEvent = CreateEvent(NULL, FALSE,FALSE, NULL);
//第三个参数:TRUE代表事件是通知类型(通知类型说明
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, 0, ThreadProc_1, NULL, 0, NULL);
hThread[1] = CreateThread(NULL, 0, ThreadProc_2, NULL, 0, NULL);
SetEvent(g_hEvent); //将时间设置为通知类型
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(g_hEvent);
return 0;
}
若通知类型事件有信号则输出
Thread_1 has already executed
Thread_2 has already executed
若为无信号则不输出任何
简单总结:
想要发送一个指令让线程们一起进行,就通知(TREUE)
反之则互斥类型
同步 = 互斥 + 有序
实现同步的方法一:
#include<stdio.h>
#include<windows.h>
HANDLE g_hSet, g_hClear;
HANDLE hMutex;
int b = 10;
int g_Number = 0; //令它为令牌
//生产者
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
int i;
for (i = 0; i < b; i++)
{
WaitForSingleObject(hMutex, INFINITE);
if (g_Number == 0)
{
g_Number = 1;
printf("我生产了一颗红薯\n");
}
else
{
i--; //因为每次判断错误后会使得i++并且没执行任何语句,所以浪费了一次循环,故令i--
}
ReleaseMutex(hMutex);
}
return 0;
}
//消费者
DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
int i;
for (i = 0; i < b; i++)
{
//互斥的访问缓冲区
WaitForSingleObject(hMutex, INFINITE);
if (g_Number == 1)
{
g_Number = 0;
printf("02吃了这个红薯\n");
}
else
{
i--;
}
ReleaseMutex(hMutex);
}
return 0;
}
int main()
{
HANDLE arrhtherd[2];
hMutex = CreateMutex(NULL, FALSE,NULL);
arrhtherd[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
arrhtherd[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
WaitForMultipleObjects(2, arrhtherd, TRUE, INFINITE);
CloseHandle(arrhtherd[0]);
CloseHandle(arrhtherd[1]);
getchar();
return 0;
}
缺点:浪费时间
比如某一刻生产者获得10ms,执行完判断,走到else,用了1ms,剩下的9ms不会浪费掉,会在重复进行刚才的判断操作,消耗掉剩下的9ms时间
方法二:用事件
#include<stdio.h>
#include<windows.h>
HANDLE g_hSet, g_hClear;
int b = 10;
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
int i;
for (i = 0; i < b; i++)
{
WaitForSingleObject(g_hSet, INFINITE);
printf("我生产了一颗红薯\n");
SetEvent(g_hClear); //设置消费者线程有信号
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
int i;
for (i = 0; i < b; i++)
{
WaitForSingleObject(g_hClear, INFINITE);
printf("02吃了这个红薯\n");
SetEvent(g_hSet);
}
return 0;
}
int main()
{
HANDLE arrhtherd[2];
g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL); //将首先要执行的生产者线程设为有信号,再在生产者线程中将消费者线程设置为有信号
g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL);
arrhtherd[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
arrhtherd[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
WaitForMultipleObjects(2, arrhtherd, TRUE, INFINITE);
CloseHandle(arrhtherd[0]);
CloseHandle(arrhtherd[1]);
getchar();
}