第五十二课 win32 临界区
1.线程安全问题
2.临界区
设计图
临界区的使用
1、创建CRITICAL_SECTION:
CRITICAL_SECTION cs;
2、在使用前进行初始化
InitializeCriticalSection(&cs);
3、在函数中使用:
DWORD WINAPI 线程A(PVOID pvParam)
{
EnterCriticalSection(&cs);
//对全局遍历X的操作
LeaveCriticalSection(&cs);
return(0);
}
DWORD WINAPI 线程B(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
//对全局遍历X的操作
LeaveCriticalSection(&g_cs);
return(0);
}
4、删除CRITICAL_SECTION
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
当线程不再试图访问共享资源时
CRITICAL_SECTION
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
:::info
LockCount:
它被初始化为数值 -1
此数值等于或大于 0 时,表示此临界区被占用
等待获得临界区的线程数:LockCount - (RecursionCount -1)
RecursionCount:
此字段包含所有者线程已经获得该临界区的次数
OwningThread:
此字段包含当前占用此临界区的线程的线程标识符
此线程 ID 与GetCurrentThreadId 所返回的 ID 相同
:::
测试代码
#include "stdafx.h"
#include <windows.h>
CRITICAL_SECTION cs;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("11111:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("22222:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("33333:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
DWORD WINAPI ThreadProc4(LPVOID lpParameter)
{
for(int x=0;x<1000;x++)
{
EnterCriticalSection(&cs);
Sleep(1000);
printf("44444:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
LeaveCriticalSection(&cs);
}
return 0;
}
int main(int argc, char* argv[])
{
InitializeCriticalSection(&cs);
//printf("主线程:%x %x %x\n",cs.LockCount,cs.RecursionCount,cs.OwningThread);
//创建一个新的线程
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread3 = ::CreateThread(NULL, 0, ThreadProc3,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread4 = ::CreateThread(NULL, 0, ThreadProc4,
NULL, 0, NULL);
//如果不在其他的地方引用它 关闭句柄
::CloseHandle(hThread1);
::CloseHandle(hThread2);
::CloseHandle(hThread3);
::CloseHandle(hThread4);
Sleep(1000*60*60);
return 0;
}
3.合理使用临界区
问题一:
令牌操作应该紧贴对变量的操作,应该把令牌操作放进循环里,不然程序则变成单线程,只有那个函数执行完释放令牌,下一个函数才能执行
4.单独锁
问题场景
解决方法:单独锁
作业
写一个死锁程序
#include <windows.h>
#include <iostream>
CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;
DWORD WINAPI ThreadProc1(LPVOID lpParam) {
EnterCriticalSection(&cs1);
cout << "Thread 1 acquired Critical Section 1" << endl;
Sleep(1000);
EnterCriticalSection(&cs2);
cout << "Thread 1 acquired Critical Section 2" << endl;
LeaveCriticalSection(&cs2);
cout << "Thread 1 released Critical Section 2" << endl;
LeaveCriticalSection(&cs1);
cout << "Thread 1 released Critical Section 1" << endl;
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParam) {
EnterCriticalSection(&cs2);
cout << "Thread 2 acquired Critical Section 2" << endl;
Sleep(1000);
EnterCriticalSection(&cs1);
cout << "Thread 2 acquired Critical Section 1" << endl;
LeaveCriticalSection(&cs1);
cout << "Thread 2 released Critical Section 1" << endl;
LeaveCriticalSection(&cs2);
cout << "Thread 2 released Critical Section 2" << endl;
return 0;
}
int main() {
InitializeCriticalSection(&cs1);
InitializeCriticalSection(&cs2);
HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
}
第五十三课 win32 互斥体
感觉和下一节课事件对象一起理解会更好理解互斥体的本质
所有线程都是读取资源而没有写入操作是不需要互斥的,且只有一个线程去写入也是不需要互斥
需要互斥的情况是:多个线程写入同一个资源或者同一个资源被多个线程读取和写入
1.WaitForSingleObject函数
个人理解:比如在线程中创建线程尤为重要,使用了的话他会线程会等待里面的线程执行完后再释放本线程,否则就会出现本线程已释放但是里面的线程还在执行的情况,具体代码参考作业
DWORD WaitForSingleObject(
HANDLE hHandle, // handle to object
DWORD dwMilliseconds // time-out interval
);
功能说明:
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变已通知状态为止.
hHandle:
内核对象句柄,可以是进程也可以是线程.
dwMilliseconds:
等待时间,单位是毫秒 。值为**INFINITE(-1)**
一直等待,超过等待时间则直接执行下面代码并返回值为102超时
返回值:
WAIT_OBJECT_0(0) 等待对象变为已通知
WAIT_TIMEOUT(0x102) 超时
特别说明:
1、内核对象中的每种对象都可以说是处于已通知或未通知的状态之中
2、这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的(相当于有一个循环函数一直在读这个句柄的状态结构体的一个参数,直到他变成已通知)
3、当线程正在运行的时候,线程内核对象处于未通知状态,当线程终止运行的时候,它就变为已通知状态
5、在内核中就是个BOOL值,运行时FALSE 结束TRUE
6、下面代码情况时需注意内核对象句柄可能在函数执行后又会把状态改成未通知,虽然线程和进程这种内核对象不会但是不能保证其他内核对象不会这样
示例代码
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for(int i=0;i<5;i++)
{
printf("+++++++++\n");
Sleep(1000);
}
return 0;
}
int main(int argc, char* argv[])
{
//创建一个新的线程
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
DWORD dwCode = ::WaitForSingleObject(hThread1, INFINITE);
MessageBox(0,0,0,0);
return 0;
}
2.WaitForMultipleObjects函数
DWORD WaitForMultipleObjects(
DWORD nCount, // number of handles in array
CONST HANDLE *lpHandles, // object-handle array
BOOL bWaitAll, // wait option
DWORD dwMilliseconds // time-out interval
);
功能说明:
同时查看若干个内核对象的已通知状态
nCount:
要查看内核对象的数量
lpHandles:
内核对象数组
bWaitAll:
等到类型 TRUE 等到所有变为已通知 FALSE 只要有一个变为已通知
dwMilliseconds:
超时时间 ;INFINITE一直等待
返回值:
bWaitAll为TRUE时,返回WAIT_OBJECT_0(0) 代码所以内核对象都变成已通知
bWaitAll为FALSE时,返回最先变成已通知的内核对象在数组中的索引
WAIT_TIMEOUT(0x102) 超时
示例代码
int main(int argc, char* argv[])
{
HANDLE hArray[2];
//创建一个新的线程
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
//创建一个新的线程
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2,
NULL, 0, NULL);
hArray[0] = hThread1;
hArray[1] = hThread2;
DWORD dwCode = ::WaitForMultipleObjects(2, hArray,FALSE,INFINITE);
MessageBox(0,0,0,0);
return 0;
}
3.跨进程的线程控制之于互斥体
示例代码
进程一:
HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");
进程二:
HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
WaitForSingleObject(g_hMutex,INFINITE);
//逻辑代码
ReleaseMutex(g_hMutex);
进程三:
HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
WaitForSingleObject(g_hMutex,INFINITE);
//逻辑代码
ReleaseMutex(g_hMutex);
互斥体与临界区的区别:
1、临界区只能用于单个进程间的线程控制.
2、互斥体可以设定等待超时(WaitForSingleObject第二个参数),但临界区不能.
3、线程意外终结时,Mutex可以避免无限等待.
4、Mutex效率没有临界区高.
5、互斥体是内核对象,临界区是非内核对象
作业(临界区和互斥体)
注意所有界面操作比如编辑框里面的数自加1,都需要另起线程去操作而不是在主线程中操作
使用临界区
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#pragma warning(disable:4996)
HWND hEdit1;
HWND hEdit2;
HWND hEdit3;
HWND hEdit4;
CRITICAL_SECTION cs;
BOOL flag = TRUE;;
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
EnterCriticalSection(&cs);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit2, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit2, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
LeaveCriticalSection(&cs);
Sleep(500);
}
return 2;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
EnterCriticalSection(&cs);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit3, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit3, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
LeaveCriticalSection(&cs);
Sleep(500);
}
return 3;
}
DWORD WINAPI ThreadProc4(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
EnterCriticalSection(&cs);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit4, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit4, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
LeaveCriticalSection(&cs);
Sleep(500);
}
return 4;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
InitializeCriticalSection(&cs);
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
HANDLE hThread3 = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);
HANDLE hThread4 = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL);
WaitForSingleObject(hThread2, INFINITE);
WaitForSingleObject(hThread3, INFINITE);
WaitForSingleObject(hThread4, INFINITE);
CloseHandle(hThread2);
CloseHandle(hThread3);
CloseHandle(hThread4);
DeleteCriticalSection(&cs);
return 1;
}
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
hEdit1 = GetDlgItem(hwndDlg, IDC_EDIT1);
SetWindowText(hEdit1,TEXT("0"));
hEdit2 = GetDlgItem(hwndDlg, IDC_EDIT2);
SetWindowText(hEdit2, TEXT("0"));
hEdit3 = GetDlgItem(hwndDlg, IDC_EDIT3);
SetWindowText(hEdit3, TEXT("0"));
hEdit4 = GetDlgItem(hwndDlg, IDC_EDIT4);
SetWindowText(hEdit4, TEXT("0"));
return TRUE;
}
case WM_CLOSE:
EndDialog(hwndDlg, 0);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_BUTTON1:
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
//::CloseHandle(hThread1);
return TRUE;
}
break;
}
return FALSE;
}
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}
使用互斥体
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#pragma warning(disable:4996)
HWND hEdit1;
HWND hEdit2;
HWND hEdit3;
HWND hEdit4;
HANDLE g_hMutex;
BOOL flag = TRUE;;
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
WaitForSingleObject(g_hMutex, INFINITE);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit2, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit2, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
ReleaseMutex(g_hMutex);
Sleep(500);
}
return 2;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
WaitForSingleObject(g_hMutex, INFINITE);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit3, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit3, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
ReleaseMutex(g_hMutex);
Sleep(500);
}
return 3;
}
DWORD WINAPI ThreadProc4(LPVOID lpParameter)
{
TCHAR szBuffer[10];
TCHAR szBuffer1[10];
DWORD dwInt;
DWORD dwInt1;
memset(szBuffer, 0, 10);
memset(szBuffer1, 0, 10);
while (flag)
{
WaitForSingleObject(g_hMutex, INFINITE);
GetWindowText(hEdit1, szBuffer, 10);
GetWindowText(hEdit4, szBuffer1, 10);
sscanf(szBuffer, "%d", &dwInt);
sscanf(szBuffer1, "%d", &dwInt1);
dwInt = dwInt - 50;
dwInt1 = dwInt1 + 50;
sprintf(szBuffer, "%d", dwInt);
sprintf(szBuffer1, "%d", dwInt1);
SetWindowText(hEdit1, szBuffer);
SetWindowText(hEdit4, szBuffer1);
if (dwInt == 0)
{
flag = false;
}
ReleaseMutex(g_hMutex);
Sleep(500);
}
return 4;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
g_hMutex = CreateMutex(NULL, FALSE, "XYZ");
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
HANDLE hThread3 = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);
HANDLE hThread4 = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL);
WaitForSingleObject(hThread2, INFINITE);
WaitForSingleObject(hThread3, INFINITE);
WaitForSingleObject(hThread4, INFINITE);
CloseHandle(hThread2);
CloseHandle(hThread3);
CloseHandle(hThread4);
return 1;
}
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch (uMsg)
{
case WM_INITDIALOG:
{
hEdit1 = GetDlgItem(hwndDlg, IDC_EDIT1);
SetWindowText(hEdit1,TEXT("0"));
hEdit2 = GetDlgItem(hwndDlg, IDC_EDIT2);
SetWindowText(hEdit2, TEXT("0"));
hEdit3 = GetDlgItem(hwndDlg, IDC_EDIT3);
SetWindowText(hEdit3, TEXT("0"));
hEdit4 = GetDlgItem(hwndDlg, IDC_EDIT4);
SetWindowText(hEdit4, TEXT("0"));
return TRUE;
}
case WM_CLOSE:
EndDialog(hwndDlg, 0);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_BUTTON1:
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
//::CloseHandle(hThread1);
return TRUE;
}
break;
}
return FALSE;
}
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
return 0;
}
问题:
1、为什么调试代码时执行SetWindowText(hEdit4, szBuffer1);后不会实时写入编辑框
2、之前编辑框1自减到负数还会一直无限自减,后面发现是flag应该设置成全局变量
3、我发现sleep要写久一点,按作业要求sleep(50)的话结果老是会多出100,因为在线程切换的20ms的间隙其他两个线程如果已经进入了循环,那么在红包总量剩0的时候,就算flag已经修改成false,但是其他两个线程的循环还是会执行完,也就会各自多出50
4、调试多线程
的时候要在各个线程都下个断点才好调试,这次是第一次调试多线程整了我老半天