第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;
}
原文地址:https://www.cnblogs.com/5iedu/p/4708237.html