先看一下下面的代码:

#include <windows.h>
#include <stdio.h>
DWORD WINAPI FirstThread(LPVOID param);
DWORD WINAPI SecondThread(LPVOID param);
const int MAX_TIMES=30;
int g_nIndex=0;
DWORD g_dwTimes[MAX_TIMES];
int main()
{
    HANDLE hThreadFirst=CreateThread(NULL,0,FirstThread,NULL,CREATE_SUSPENDED,NULL);
    HANDLE hThreadSecond=CreateThread(NULL,0,SecondThread,NULL,CREATE_SUSPENDED,NULL);
    SetThreadPriority(hThreadSecond,THREAD_PRIORITY_ABOVE_NORMAL);//这里为了创造线程2先启动的条件,给它提升了一下优先级
    ResumeThread(hThreadFirst);
    ResumeThread(hThreadSecond);
    HANDLE hThread[2];
    hThread[0]=hThreadFirst;
    hThread[1]=hThreadSecond;
    DWORD dw=WaitForMultipleObjects(2,hThread,true,INFINITE);
    int n=GetLastError();
    for(int i=0;i<MAX_TIMES;i++)
    {
        printf("%lu\n",g_dwTimes[i]);
    }
    CloseHandle(hThreadFirst);
    CloseHandle(hThreadSecond);
    system("pause");
    return 0;
}

DWORD WINAPI FirstThread(LPVOID param)
{
    DWORD temp;
    while(g_nIndex<MAX_TIMES)
    {
        g_dwTimes[g_nIndex]=temp=GetTickCount();
        Sleep(10);//因得出的时间基本一致,故加了延时,而且,Sleep导致本线程放弃时间片,这样的话,另一个线程肯定能得到时间片,这也是我想要的执行代码过程中切换的效果
        g_nIndex++;
    }
    return 0;
}

DWORD WINAPI SecondThread(LPVOID param)
{
    DWORD temp;
    while(g_nIndex<MAX_TIMES)
    {
        g_nIndex++;
        Sleep(10);
        g_dwTimes[g_nIndex-1]=temp=GetTickCount();
    }
    return 0;
}

如果SecondThread先运行,那么,g_index变为1,由于Sleep导致FirstThread得到CPU时间,那么代码就先填充g_dwTimes[1],执行完填充,又有Sleep,此时,FirstThread放弃时间片,SecondThread得到时间片,然后g_dwTimes[0]再填充,这样g_dwTimes[0]就比g_dwTimes[1]的值要大

测试效果

ios关键代码泄露怎么办 关键代码怎么写_临界区或关键代码段


其实,想要使这种问题不发生,就要使线程在资源访问的过程中绝对不会被打断

那么我们就可以使用关键代码段了,使用关键代码段之前,需要先定义一个CRITICAL_SECTION数据结构g_cs;

使用关键代码段之前需要初始化g_cs,不初始化的会有无法预料的结果

InitializeCriticalSection(&g_cs);

若是不想用关键代码段了,就DeleteCriticalSection(&g_cs);

打个形象的比方 EnterCriticalSection就像进厕所,抽水马桶是你需要保护的数据,进去要把门的标志变为有人

这样别人看到有人的标志就不会进入

LeaveCriticalSection,出厕所,然后把门的标志变为无人,只有在无人的情况下,别人才可以进去使用马桶(访问数据)了

不废话了,我们看看修正后的代码

#include <windows.h>
#include <stdio.h>
DWORD WINAPI FirstThread(LPVOID param);
DWORD WINAPI SecondThread(LPVOID param);
const int MAX_TIMES=30;
int g_nIndex=0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;
int main()
{
    InitializeCriticalSection(&g_cs);
    HANDLE hThreadFirst=CreateThread(NULL,0,FirstThread,NULL,CREATE_SUSPENDED,NULL);
    HANDLE hThreadSecond=CreateThread(NULL,0,SecondThread,NULL,CREATE_SUSPENDED,NULL);
    SetThreadPriority(hThreadSecond,THREAD_PRIORITY_ABOVE_NORMAL);
    ResumeThread(hThreadFirst);
    ResumeThread(hThreadSecond);
    HANDLE hThread[2];
    hThread[0]=hThreadFirst;
    hThread[1]=hThreadSecond;
    DWORD dw=WaitForMultipleObjects(2,hThread,true,INFINITE);
    int n=GetLastError();
    for(int i=0;i<MAX_TIMES;i++)
    {
        printf("%lu\n",g_dwTimes[i]);
    }
    CloseHandle(hThreadFirst);
    CloseHandle(hThreadSecond);
    DeleteCriticalSection(&g_cs);
    system("pause");
    return 0;
}

DWORD WINAPI FirstThread(LPVOID param)
{
    DWORD temp;
    while(g_nIndex<MAX_TIMES)
    {
        EnterCriticalSection(&g_cs);
        g_dwTimes[g_nIndex]=temp=GetTickCount();
        Sleep(10);//因得出的时间基本一致,故加了延时
        g_nIndex++;
        LeaveCriticalSection(&g_cs);
    }
    return 0;
}

DWORD WINAPI SecondThread(LPVOID param)
{
    DWORD temp;
    while(g_nIndex<MAX_TIMES)
    {
        EnterCriticalSection(&g_cs);
        g_nIndex++;
        Sleep(10);
        g_dwTimes[g_nIndex-1]=temp=GetTickCount();
        LeaveCriticalSection(&g_cs);
    }
    return 0;
}

测试结果

ios关键代码泄露怎么办 关键代码怎么写_内核态_02


这样结果是不是就正常了

关键代码的优点是使用方便,运行快速,缺点是只能在进程内使用(不能跨进程)

另外EnterCriticalSection如果在进入关键代码时连续调用两次,那么出关键代码时也要调用LeaveCriticalSection两次

由于EnterCriticalSection会使线程进入等待状态(内核状态),有时我们不想让线程进入等待状态,还可以使用另外一个函数
TryEnterCriticalSection,相反该函数绝不允许线程进入等待状态,它只会返回TRUE 或者 FALSE,如果调用此函数时,发现资源已被另一个函数使用,就返回FALSE,否则返回TRUE;
运用这个函数,看看线程能不能访问某些资源,如果不能访问,就转而执行其他的操作,否则就去访问资源

在多处理器上,当前拥有资源的线程可以在不同的处理器上运行,并且可以很快的放弃对资源的控制,然而,EnterCriticalSection一旦发现无法访问资源,就会立即使线程进入等待状态(内核态),这种用户态到内核态的转换可能需要1000个CPU周期,,等到他转到内核态,由于已经可以访问资源了,又必须立即从内核态转到用户态,这极大的浪费了CPU的时间。然而,另一个资源从控制资源到放弃控制资源可能只有几十个CPU周期,我们完全可以在这期间进行多次尝试,在尝试进入关键代码失败一定次数后再转入到内核态,这样就减少了内核态和用户态之间反复切换造成的CPU消耗,提高效率(仅在多核处理器上)

BOOL InitializeCriticalSectionAndSpinCount(
LPCRITICAL_SECTION lpCriticalSection, //关键代码段的结构
DWORD dwSpinCount // 自旋次数
);
第二个参数,一般默认为4000,这个参数只在多处理器计算机上有效,如果再单核计算机上,这个参数会被忽略
DWORD SetCriticalSectionSpinCount(
LPCRITICAL_SECTION lpCriticalSection,
DWORD dwSpinCount // 自旋次数
);即使关键代码段已经初始化完成,也可以设置自旋次数

关键代码段的错误处理:
1.首先当内存不足时,InitializeCriticalSection函数分配调试信息块可能失败,然后会出现STATUS_NO_MEMORY的错误,可以使用结构化异常处理来处理这个错误
2.使用InitializeCriticalSectionAndSpinCount,如果无法分配调试信心块的内存,将会直接返回false
3.使用关键代码段的另一个问题是,如果两个或多个线程同时运行到EnterCriticalSection(形成了多个线程争用关键代码段的局面),此时关键代码段将创建一个事件内核对象,这当然没问题,但是当内存不足时,事件内核对象的创建就可能失败,此时EnterCriticalSection将会产生一个EXCEPTION_INVALID_HANDLE的异常

当发生异常时
1.我们可以使用结构化异常处理来跟踪这个错误,如果错误发生,我们可以不访问关键代码段,等待内存变为可用状态,然后再次EnterCriticalSection
2. 使用InitializeCriticalSectionAndSpinCount函数,并设置第二个参数的高信息位,这样,函数会自动创建事件内核对象,并将它与关键代码关联,如果无法创建成功,自然返回false,这样就更早的抓到这个错误,我们可以更加妥善的解决这个问题,如果返回true,我们就不用担心了,事件内核对象已经创建完成

使用关键代码段的技巧

1.每个共享资源使用一个CRITICAL_SECTION变量
2.同时访问多个资源,必须按照完全相同的顺序请求对资源的访问
3.不要长时间运行关键代码段,这样别的线程就会进入很长时间的等待状态,这不利于程序性能的提高