• 第7章 线程调度、优先级和亲缘性(1)


    7.1 线程的挂起和恢复

    (1)线程挂起

      ①创建时(如CreateProcess、CreateThread),传入CREATE_SUSPENDED标志

      ②用SuspendThread挂起线程。这个函数可以挂起自己,也可以挂起其它线程(只要有线程句柄)

      ③调用SuspendThread时,如果这时线程执行在用户态,线程会马上被挂起。如果调用SuspendThread时线程己经执行在内核态时,SuspendThread会异步返回,而线程并不会马上暂停。但当该线程从内核态又转为用户态时,则会立即被暂停。

      ④当调用SuspendThread挂起线程时,我们并不知道线程在做什么?如果此时A线程正在分配堆中的内存,则它将会锁定堆,这会导致此时也要访问堆的B线程被中止,直到A恢复,而这可能引起其他问题或者死锁。所以只有在确切目标线程在哪里(或在做什么时)调用SuspendThread才是安全的。

      ⑤线程在挂机计数不为0或没有消息队列没有消息时,是不可调度的(没时间片)

    (2)线程恢复ResumeThread,返回前一个挂起计数,否则返回0xFFFFFFFF。

      ①一个线程可以被多次挂起,最多可以挂起MAXIMUNM_SUSPEND_COUNT(127)次

      ②线程被挂起多次时,只有恢复到挂起计数为0时,才可以重新被调度。

    (3)可以调用GetSystemTimeAdjustment来查看线程切换的周期(大约20ms)

    7.2 进程的挂起和恢复

    (1)进程的挂起——挂起进程中所有的线程(注意进程本身是不可调度的)

      ①Windows没有提供挂起进程中所有线程的方法,因为存在竞态条件问题。(如在线程被挂起时,可能创建一个新的线程。系统必须想方设法挂起这个时间段内创建任何新线程。

      ②自定义的SuspendProcess函数(用来挂起所有线程)

    【SuspendProcess程序】

    #include <windows.h>
    #include <stdio.h>
    #include <Tlhelp32.h>
    #include <tchar.h>
    #include <locale.h>
    
    //挂起进程中所有的线程
    void SuspendProcess(DWORD dwProcessID, BOOL fSuspend)
    {
        DWORD dwThreadID = GetCurrentThreadId(); //主调线程ID
    
        //获取系统中所有线程列表,第2个参数为0时表示当前进程
        HANDLE hSnapshot = CreateToolhelp32Snapshot
                               (TH32CS_SNAPTHREAD, dwProcessID);
        if (hSnapshot != INVALID_HANDLE_VALUE){
            /*
    以下在枚举线程过程中,可能有新的线程创建,也可能有线程被销毁。但CreateToolhelp32SnapShot只是快照,无法反应这一变化。 所以新的线程就不会被挂机。同时,被销毁的线程ID可能被另一个进程中的线程给占用,这会造成误挂其他进程中的线程的潜在风险。因此这个函数要慎用。 */ //遍历线程列表 THREADENTRY32 te = { sizeof(te) }; BOOL fOk = Thread32First(hSnapshot, &te); for (; fOk;fOk=Thread32Next(hSnapshot,&te)){ //线程是否在目标进程中 if (te.th32OwnerProcessID != dwProcessID) continue;; //尝试将线程ID转换为线程句柄(可挂机和恢复线程) HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); if (hThread != NULL){ //挂机或恢复线程 if (fSuspend) { //防止主调函数挂起自己,导致循环无法进行而不能挂机其他线程 if (te.th32ThreadID != dwThreadID) SuspendThread(hThread); } else ResumeThread(hThread); } CloseHandle(hThread); } CloseHandle(hSnapshot); //如果要挂机进程,最后挂起主调进程 if (fSuspend){ HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, dwThreadID); if(hThread) SuspendThread(hThread); CloseHandle(hThread); } } } //线程函数 DWORD WINAPI ThreadProc(PVOID pParam) { while (TRUE){ _tprintf(_T("线程(ID:0x%04X),正在输出... 时间%d "), GetCurrentThreadId(), GetTickCount()); Sleep(1000); } return 0; } int _tmain() { _tsetlocale(LC_ALL, _T("chs")); //创建两个线程,线程刚创建时是挂起的——这两个线程用来测试SuspendProcess函数 HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, NULL, CREATE_SUSPENDED, NULL); HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, NULL, CREATE_SUSPENDED, NULL); CloseHandle(hThread1); CloseHandle(hThread2); //唤醒所有线程 _tprintf(_T("正在唤醒线程... ")); SuspendProcess(GetCurrentProcessId(), FALSE); _tsystem(_T("PAUSE")); //在线程运行过程, 按任意键可以挂起进程 _tprintf(_T("进程己被挂起! ")); SuspendProcess(GetCurrentProcessId(), TRUE); //因所有线程都被挂起,进程中无活动线程,故进程(线)程无法被唤醒 //所以后面的代码无法执行! //再次唤醒所有线程 _tprintf(_T("正在唤醒线程... ")); SuspendProcess(GetCurrentProcessId(), FALSE); _tsystem(_T("PAUSE")); return 0; }

    7.3 睡眠——void Sleep(DWORD dwMilliseconds)函数

    (1)调用Sleep函数,将使线程自愿放弃属于它的时间片中剩下的部分

    (2)dwMilliseconds只是近似的毫秒数。因为Windows不是实时操作系统,可以过了这个时间段,系统仍在调用其他线程。

    (3)dwMilliseconds为INFINITE时,表示永远不要调用这个线程(但这不好,如果不调用线程,应该让其退出

    (4)dwMilliseconds为0时,主动放弃剩余时间片。但如果没有更高优先级线程(优先级≥本线程)可调度时,这个线程可能被重新调度,即使低优先级线程仍处在饥饿状态

    7.4 切换到另一个线程——BOOL SwitchToThread();

    (1)调用此函数时,系统会查看是否存在正饥饿线程,如果没有立即返回,调用线程继续执行。如果有,则会调用其他线程(但与Sleep(0)不同,对另一个线程优先级没有要求,其优先级可以比调用函数的线程低

    (2)该函数允许一个需要资源的线程强制另一个优先级较低、而目前却拥有该资源的线程放弃该资源。

    (3)返回值:如果没有其他线程可以调度,返回FALSE。否则返回非零

    7.5 在超线程CPU上切换到另一个线程

     

    (1)超线程处理器芯片上有多个“逻辑CPU,每个都可以运行一个线程(注意,与多核CPU不同!)。这样的处理器一般需要多加入一个Logical CPU Pointer(逻辑处理单元)每个线程都有自己的状态寄存器。而其余部分如ALU(整数运算单元)、FPU(浮点运算单元)、L2 Cache(二级缓存)则保持不变,这些部分被共享的

    (2)虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的CPU那样,每个CPU都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU的性能。

    (3)当一个线程中止时,CPU自动执行另一个线程,无需操作系统干预。

    (4)在超线程CPU上执行一个旋转循环(spin loop)代码时,我们需要强制当前线程暂停,从而另一个线程可以使用芯片资源。在Win32API中,可以调用YieldProcessor宏来强制让出CPU。

    7.6 线程的执行时间(3种方法:)

    (1)求时间的3种方法

    方法

    函数举例

    精度

    基于一般计数器

    GetTickCount

    GetProcessTimes

    GetThredTimes

    依赖系统内部10~15ms的计时器,可达毫秒级,精度低,也无法精确到1ms

    基于高精度晶振频率的时间戳计数器

    (推存使用)

    QuerPerformanceCounter

    可达微秒级。精度较高

    基于CPU频率的时间戳计数器

    ReadTimeStampCounter

    精度可高达1/CPU主频(Hz) 秒,即纳秒级

    (2)与GetTickCount功能类似的函数(计时的时候会包含线程被中断的时间!

    函数

    描述

    单位

    GetTickCount

    返回自计算机启动以来的毫秒数

    单位为毫秒,精度低

    QueryPerformanceCounter

    返回自计算机启动以来的高精度晶振的计数器

    单位为计数值,须换算成时间转换方法见后面

    ReadTimeStampCounter

    返回自计算机启动以来的CPU时钟周期数

    单位为计数值,须换算成时间转换方法见后面

    (3)占用CPU时间的函数比较

    函数

    描述

    单位

    GetProcessTimes GetThreadTimes

    返回进(线)程占用CPU的时间

    (这个时间是基于一般计数器的

    100ns

    QueryThreadCircleTime

    QueryProcessCircleTime

    返回进(线)程占用CPU的时钟周期数

    (这个时钟周期计数是基于CPU频率

    计数值,须转换为时间(转换方法见后面)。精度更高

    【ThreadTimes程序】用来演示求线程执行时间的方法

     

    #include <windows.h>
    #include <tchar.h>
    #include <malloc.h>
    #include <locale.h>
    #include <time.h>
    
    #define CPUIndexToMask(dwCPUIndex)  (1<<(dwCPUIndex))
    
    DWORD WINAPI ThreadProc(PVOID pParam)
    {
        DWORD  dwStart = GetTickCount();
        HANDLE hEvent = (HANDLE)pParam;
        srand((unsigned int)time(NULL));
        
        float fRangeAvg = 0.0f;
        float fCnt = 0.0f;
    
        while (TRUE)
        {
            //将每次产生的随机数求平均值
            fCnt += 1.0f;
            fRangeAvg += (float)rand();
            fRangeAvg /= fCnt;
            // dwMilliseconds=0,线程会测试事件对象的状态,并立即返回。
            if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 0))
                break;
        }
        //模拟一个释放CPU的操作,这个时间会被GetTickCount计算错误统计在内
        Sleep(1000);
        DWORD  dwEnd = GetTickCount();
    
        _tprintf(_T("线程[ID:0x%X]运行时间(GetTickCount计算):%ums
    "), 
                 GetCurrentThreadId(),dwEnd-dwStart);
        return (DWORD)fCnt; //返回循环的次数
    }
    
    int _tmain()
    {
        _tsetlocale(LC_ALL, _T("chs"));
    
        //创建停止事件
        HANDLE hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
        //获取CPU个数
        SYSTEM_INFO  si = {0};
        GetSystemInfo(&si);
        const DWORD dwCPUCnt = si.dwNumberOfProcessors;
    
        //创建线程句柄数组
        HANDLE* hThread = (HANDLE*)malloc(dwCPUCnt*sizeof(HANDLE));
        DWORD dwThreadID;
    
        //创建线程
        for (DWORD i = 0; i < dwCPUCnt;i++){
            hThread[i] = CreateThread(NULL, 0,
                            ThreadProc, hStopEvent, 
                            CREATE_SUSPENDED, &dwThreadID);
            //将线程分配在不同的CPU上
            //SetThreadAffinityMask(hThread[i], CPUIndexToMask(i));
            _tprintf(_T("线程[ID:0x%X]运行在CPU(%d)上
    "), dwThreadID, i);
            ResumeThread(hThread[i]);
        }
    
        //如果每个CPU都安排了一个线程的话,此时应看到所有CPU占用率几乎都是100%
        _tprintf(_T("共%d个线程,全部创建完毕,请查看任务管理器中CPU使用率
    "), dwCPUCnt);
        _tsystem(_T("PAUSE"));
    
        //通知所有的线程停止
        SetEvent(hStopEvent);
        
        //等待所有线程退出
        WaitForMultipleObjects(dwCPUCnt, hThread, TRUE, INFINITE);
        DWORD  dwExitCode;
        FILETIME  tmCreation = {0};
        FILETIME  tmExit = { 0 };
        FILETIME  tmKernel = { 0 };
        FILETIME  tmUser = { 0 };
        SYSTEMTIME  tmSys = { 0 };
    
        ULARGE_INTEGER bigTmp1 = { 0 };//2个DWORD,即64位
        ULARGE_INTEGER bigTmp2 = { 0 };
    
        //取出线程退出代码,此例中就是每个线程内循环的次数。
        //统计线程运行时间,并关闭所有线程内核对象
        for (DWORD i = 0; i < dwCPUCnt;i++){
            GetExitCodeThread(hThread[i], &dwExitCode);
            _tprintf(_T("线程[H:0x%08X]退出,退出码(%u),以下为时间统计;
    "),
                          hThread[i],dwExitCode);
            GetThreadTimes(hThread[i], &tmCreation, &tmExit, &tmKernel, &tmUser);
            //创建时间
            FileTimeToLocalFileTime(&tmCreation, &tmCreation);
            FileTimeToSystemTime(&tmCreation, &tmSys);
            _tprintf(_T("	创建时间:%02u:%02u:%02u.%04u
    "),
                     tmSys.wHour,tmSys.wMinute,tmSys.wSecond,tmSys.wMilliseconds);
    
            //退出时间
            FileTimeToLocalFileTime(&tmExit, &tmExit);
            FileTimeToSystemTime(&tmExit, &tmSys);
            _tprintf(_T("	退出时间:%02u:%02u:%02u.%04u
    "),
                     tmSys.wHour, tmSys.wMinute, tmSys.wSecond, tmSys.wMilliseconds);
            
            //线程存活时间(=线程退出时间-创建时间)
            //FILETIME中的时间单位为100ns,除以10000才是毫秒
            bigTmp1.HighPart = tmCreation.dwHighDateTime;
            bigTmp1.LowPart = tmCreation.dwLowDateTime;
            bigTmp2.HighPart = tmExit.dwHighDateTime;
            bigTmp2.LowPart = tmExit.dwLowDateTime;
            _tprintf(_T("	线程存活时间:%I64dms
    "), 
                         (bigTmp2.QuadPart-bigTmp1.QuadPart)/10000);
    
            //内核时间
            bigTmp1.HighPart = tmKernel.dwHighDateTime;
            bigTmp1.LowPart = tmKernel.dwLowDateTime;
            _tprintf(_T("	内核模式(RING0)耗时:%I64dms
    "),bigTmp1.QuadPart /10000);
    
            //用户时间
            bigTmp2.HighPart = tmUser.dwHighDateTime;
            bigTmp2.LowPart = tmUser.dwLowDateTime;
            _tprintf(_T("	用户模式(RING3)耗时:%I64dms
    "), bigTmp2.QuadPart / 10000);
    
            //实际占用CPU时间(=内核耗时+用户耗时)
            bigTmp2.HighPart = tmUser.dwHighDateTime;
            bigTmp2.LowPart = tmUser.dwLowDateTime;
            _tprintf(_T("	实际占用CPU时间:%I64dms
    "),
                     (bigTmp1.QuadPart+bigTmp2.QuadPart) / 10000);
    
            //关闭线程句柄
            CloseHandle(hThread[i]);
        }
        
        free(hThread);
        CloseHandle(hStopEvent);
        _tsystem(_T("PAUSE"));
        return 0;
    }

    (3)基于晶振频率计数器转换为时间的方法(方法见TimeStampCounter程序的TStopWatch

    //CStopWatch晶振计时类,基于主板晶振频率的时间戳计数器的时间类
    class CStopWatch{
    private:
        LARGE_INTEGER m_liPerfFreq;  //频率,表示每秒的计数值
        LARGE_INTEGER m_liPerfStart; //调用Start函数时的计数值
    public:
        CStopWatch();  //构造函数
        void Start();  
        __int64 Now() const; //计算自调用Start()函数以来的毫秒数
        __int64 NowInMicro() const; //计数自调用Start()函数以来的微秒数
    };
    
    CStopWatch::CStopWatch(){
        QueryPerformanceFrequency(&m_liPerfFreq);//求晶振的频率(次数/秒)
        Start();
    }
    
    void CStopWatch::Start(){
        QueryPerformanceCounter(&m_liPerfStart); //开始时的计数值
    }
    
    __int64 CStopWatch::Now() const{  //调用Start函数以来的毫秒数
        LARGE_INTEGER  liPerfNow; //
        QueryPerformanceCounter(&liPerfNow);
    
        //将次数转为时间(毫秒),注意频率的单位为秒
         return   ((liPerfNow.QuadPart - m_liPerfStart.QuadPart)*1000) / 
                                            m_liPerfFreq.QuadPart;   
    }
    
    __int64 CStopWatch::NowInMicro() const{
        LARGE_INTEGER  liPerfNow; //
        QueryPerformanceCounter(&liPerfNow);
    
        //将次数转为时间(微秒),注意频率的单位为秒
        return   ((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000000) /  
                                     m_liPerfFreq.QuadPart;
    }

    (4)基于CPU频率的时间戳计数器转换为时间的方法

      ①时间戳计数器简介(Time Stamp Counter,TSC)

        A、该计时器为自计算机启动以来的时钟周期数(计数值)

        B、从Pentium开始,所有的Intel 80x86 CPU就都又包含一个64位的时间戳计数器(TSC)的寄存器。该寄存器实际上是一个不断增加的计数器,它在CPU的每个时钟信号到来时加1(也即每一个 clock-cycle输入CPU时,该计数器的值就加1)。

        C、利用CPU的TSC,操作系统通常可以得到更为精准的时间度量。假如clock-cycle的频率是400MHZ,那么TSC就将每2.5纳秒增加一次。

        D、可用宏ReadTimeStampCounter或内部函数__rdtsc来获取当前TSC值。

      ②TSC计数值转化为时间的方法:ReadTimeStampCounter/CPU主频

      ③求CPU主频的两种方法:

        A、查注册表法:HKLM→HARDAWARE→DESCRIPTION→System→CentralProcessor→0→~MHz

        B、通过TStopWatch类求CPU主频的近似值:(方法见TimeStampCounter程序的GetCPUFrequencyInMHz函数)

    //求CPU主频,注意这是基于CPU的主频,与主板晶振的频率QueryPerformanceFrequency属不同概念!
    DWORD GetCPUFrequencyInMHz(){
        //改变线程优先级以确保线程当Sleep()后有更多的调度时间
        int currentPriority = GetThreadPriority(GetCurrentThread());
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    
        __int64 elapsedTime = 0;  //己耗时
        CStopWatch stopWatch;   //利用晶振计时类来求CPU主频
                                //默认为当前时间
        
        __int64 perfStartTime = stopWatch.NowInMicro();
        
        //获取当前时CPU时间戳的计数值
        unsigned __int64 cyclesOnStart = ReadTimeStampCounter();
        
        //等待1秒,以便求1秒左右的时间间隔内CPU主频的平均值
        Sleep(1000);
        
        //获取1秒后的己耗的CPU时间戳周期数
        unsigned __int64 numberOfCycles = ReadTimeStampCounter() - cyclesOnStart;
    
        //利用晶振计时类求时间间隔
        elapsedTime = stopWatch.NowInMicro() - perfStartTime;
    
        //恢复线程的优先级
        SetThreadPriority(GetCurrentThread(), currentPriority);
    
        //计数CPU主频 = 己耗的CPU周期数/时间  (单位:次/μs 或  MHz)
        //注意:1MHz=1000000Hz,1s=1000000μs
        DWORD  dwCPUFrequency = (DWORD)(numberOfCycles / elapsedTime);
        return dwCPUFrequency;
    }

      ④该种方法的缺点:精度虽高,但数据抖动比较厉害,在节能模式的时候结果偏慢,超频模式的时候又偏快,而且用电池和接电源的时候效果也不一样。此外用这种方法求时间还得考虑用SetThreadPriority提高优先级尽量独占时间片。并使用SetThreadAffinityMask以确保每次调用QueryPerformanceCounter的时候在同一CPU核心上

    【TimeStampCounter程序】利用TSC来计算线程时间

     

    #include <windows.h>
    #include <tchar.h>
    #include <locale.h>
    #include <time.h>
    #include "StopWatch.h"
    
    //////////////////////////////////////////////////////////////////////////
    //线程函数
    DWORD WINAPI ThreadProc(PVOID pvParam)
    {
        CStopWatch sw;
        HANDLE hEvent = (HANDLE)pvParam;
        srand((unsigned int)time(NULL));
    
        float fRangeAvg = 0.0f;
        float fCnt = 0.0f;
        while (TRUE){
    
            //将每次产生的随机数求平均值
            fCnt += 1.0f;
            fRangeAvg += (float)rand();
            fRangeAvg /= fCnt;
    
            if (WAIT_OBJECT_0 ==WaitForSingleObject(hEvent, 0)) 
                break;
        }
    
        //模拟一个释放CPU的操作,这个时间会被GetTickCount计算错误统计在内
        Sleep(1000);
        __int64 elapseTime = sw.NowInMicro();
    
        return (DWORD)elapseTime;
    }
    int _tmain()
    {
        _tsetlocale(LC_ALL, _T("chs"));
        DWORD         dwCPUFreq;
        dwCPUFreq = GetCPUFrequencyInMHz();
        //创建事件对象,用于等待线程退出
        HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, hEvent, 0, NULL);
    
        _tprintf(_T("线程正在运行中,请按任意键结束!
    "));
        _gettchar();
    
        //通知线程结束
        SetEvent(hEvent);
        WaitForSingleObject(hThread, INFINITE);
    
        ULONG64 threadCircleTime;
        QueryThreadCycleTime(hThread, &threadCircleTime);
        DWORD dwElapseTime;
        GetExitCodeThread(hThread, &dwElapseTime);
    
        LARGE_INTEGER liFreq;
    
        QueryPerformanceFrequency(&liFreq);
    
        _tprintf(_T("
    运行信息(TSC法):
    "));
        _tprintf(_T("	CPU主频 :%dMHz
    "), dwCPUFreq);
        _tprintf(_T("	晶振频率:%dMHz
    "), liFreq.QuadPart / 1000);
        _tprintf(_T("	线程存活时间为:%dms(%dμs)
    "), dwElapseTime / 1000, dwElapseTime);
        _tprintf(_T("	线程占用的CPU计数:%I64d(次)
    "), threadCircleTime);
        _tprintf(_T("	线程占用的CPU时间:%I64d(μs)
    "), threadCircleTime / dwCPUFreq);
    
        FILETIME  tmCreation = { 0 };
        FILETIME  tmExit = { 0 };
        FILETIME  tmKernel = { 0 };
        FILETIME  tmUser = { 0 };
        SYSTEMTIME  tmSys = { 0 };
        ULARGE_INTEGER bigTmp1 = { 0 };//2个DWORD,即64位
        ULARGE_INTEGER bigTmp2 = { 0 };
        GetThreadTimes(hThread, &tmCreation, &tmExit, &tmKernel, &tmUser);
    
        _tprintf(_T("
    运行信息(GetThreadTimes法):
    "));
    
        bigTmp1.HighPart = tmCreation.dwHighDateTime;
        bigTmp1.LowPart = tmCreation.dwLowDateTime;
        bigTmp2.HighPart = tmExit.dwHighDateTime;
        bigTmp2.LowPart = tmExit.dwLowDateTime;
        _tprintf(_T("	线程存活时间:%I64dms
    "),
                 (bigTmp2.QuadPart - bigTmp1.QuadPart) / 10000);
    
        bigTmp1.HighPart = tmKernel.dwHighDateTime;
        bigTmp1.LowPart = tmKernel.dwLowDateTime;
        bigTmp2.HighPart = tmUser.dwHighDateTime;
        bigTmp2.LowPart = tmUser.dwLowDateTime;
        _tprintf(_T("	线程占用的CPU时间:%I64d(μs)
    "),
                 (bigTmp1.QuadPart + bigTmp2.QuadPart)/10);
    
        CloseHandle(hEvent);
        CloseHandle(hThread);
        _tsystem(_T("PAUSE"));
    
        return 0;
    }

    //StopWatch.h

    #pragma once
    
    #include <windows.h>
    //////////////////////////////////////////////////////////////////////////
    //CStopWatch晶振计时类,基于主板晶振频率的时间戳计数器的时间类
    class CStopWatch{
    private:
        LARGE_INTEGER m_liPerfFreq;  //频率,表示每秒的计数值
        LARGE_INTEGER m_liPerfStart; //调用Start函数时的计数值
    public:
        CStopWatch();  //构造函数
        void Start();
        __int64 Now() const; //计算自调用Start()函数以来的毫秒数
        __int64 NowInMicro() const; //计数自调用Start()函数以来的微秒数
    };
    
    //////////////////////////////////////////////////////////////////////////
    DWORD GetCPUFrequencyInMHz();

    //StopWatch.c

    #include "StopWatch.h"
    
    //////////////////////////////////////////////////////////////////////////
    CStopWatch::CStopWatch(){
        QueryPerformanceFrequency(&m_liPerfFreq);//求晶振的频率(次数/秒)
        Start();
    }
    
    void CStopWatch::Start(){
        QueryPerformanceCounter(&m_liPerfStart); //开始时的计数值
    }
    
    __int64 CStopWatch::Now() const{  //调用Start函数以来的毫秒数
        LARGE_INTEGER  liPerfNow; //
        QueryPerformanceCounter(&liPerfNow);
    
        //将次数转为时间(毫秒),注意频率的单位为秒
        return   ((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000) /
            m_liPerfFreq.QuadPart;
    }
    
    __int64 CStopWatch::NowInMicro() const{
        LARGE_INTEGER  liPerfNow; //
        QueryPerformanceCounter(&liPerfNow);
    
        //将次数转为时间(微秒),注意频率的单位为秒
        return   ((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000000) /
            m_liPerfFreq.QuadPart;
    }
    
    //////////////////////////////////////////////////////////////////////////
    //求CPU主频,注意这是基于CPU的主频,与主板晶振的频率QueryPerformanceFrequency属不同概念!
    DWORD GetCPUFrequencyInMHz(){
        //改变线程优先级以确保线程当Sleep()后有更多的调度时间
        int currentPriority = GetThreadPriority(GetCurrentThread());
        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
    
        __int64 elapsedTime = 0;  //己耗时
        CStopWatch stopWatch;   //利用晶振计时类来求CPU主频
                                //默认为当前时间
    
        //获取当前时CPU时间戳的计数值
        unsigned __int64 cyclesOnStart = ReadTimeStampCounter();
    
        //等待1000ms,以便求1秒左右的时间间隔内CPU主频的平均值
        Sleep(1000);
    
        //获取1秒后的己耗的CPU时间戳周期数
        unsigned __int64 numberOfCycles = ReadTimeStampCounter() - cyclesOnStart;
    
        //利用晶振计时类求时间间隔
        elapsedTime = stopWatch.NowInMicro();//单位是μs
    
        //恢复线程的优先级
        SetThreadPriority(GetCurrentThread(), currentPriority);
    
        //计数CPU主频 = 己耗的CPU周期数/时间  (单位:次/μs 或  MHz)
        //注意:1MHz=1000000Hz,1s=1000000μs
        DWORD  dwCPUFrequency = (DWORD)(numberOfCycles / elapsedTime);
        return dwCPUFrequency;
    }
  • 相关阅读:
    JavaScript
    CSS
    HTML5&CSS
    I2C mux和复杂拓扑
    如何实例化I2C设备
    SMBus 协议
    I2C 协议
    I2C和SMBus简介
    ubuntu20.04系统下更新Raspberry Pi4的DTB
    通过configfs配置的Linux USB gadget
  • 原文地址:https://www.cnblogs.com/5iedu/p/4708237.html
Copyright © 2020-2023  润新知