第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程序】

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

#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),正在输出... t时间%dn"), 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("正在唤醒线程...n")); SuspendProcess(GetCurrentProcessId(), FALSE); _tsystem(_T("PAUSE")); //在线程运行过程, 按任意键可以挂起进程 _tprintf(_T("进程己被挂起!n")); SuspendProcess(GetCurrentProcessId(), TRUE); //因所有线程都被挂起,进程中无活动线程,故进程(线)程无法被唤醒 //所以后面的代码无法执行! //再次唤醒所有线程 _tprintf(_T("正在唤醒线程...n")); 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上切换到另一个线程

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

(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程序】用来演示求线程执行时间的方法

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

#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计算):%umsn"), 
             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)上n"), dwThreadID, i);
        ResumeThread(hThread[i]);
    }

    //如果每个CPU都安排了一个线程的话,此时应看到所有CPU占用率几乎都是100%
    _tprintf(_T("共%d个线程,全部创建完毕,请查看任务管理器中CPU使用率n"), 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),以下为时间统计;n"),
                      hThread[i],dwExitCode);
        GetThreadTimes(hThread[i], &tmCreation, &tmExit, &tmKernel, &tmUser);
        //创建时间
        FileTimeToLocalFileTime(&tmCreation, &tmCreation);
        FileTimeToSystemTime(&tmCreation, &tmSys);
        _tprintf(_T("t创建时间:%02u:%02u:%02u.%04un"),
                 tmSys.wHour,tmSys.wMinute,tmSys.wSecond,tmSys.wMilliseconds);

        //退出时间
        FileTimeToLocalFileTime(&tmExit, &tmExit);
        FileTimeToSystemTime(&tmExit, &tmSys);
        _tprintf(_T("t退出时间:%02u:%02u:%02u.%04un"),
                 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("t线程存活时间:%I64dmsn"), 
                     (bigTmp2.QuadPart-bigTmp1.QuadPart)/10000);

        //内核时间
        bigTmp1.HighPart = tmKernel.dwHighDateTime;
        bigTmp1.LowPart = tmKernel.dwLowDateTime;
        _tprintf(_T("t内核模式(RING0)耗时:%I64dmsn"),bigTmp1.QuadPart /10000);

        //用户时间
        bigTmp2.HighPart = tmUser.dwHighDateTime;
        bigTmp2.LowPart = tmUser.dwLowDateTime;
        _tprintf(_T("t用户模式(RING3)耗时:%I64dmsn"), bigTmp2.QuadPart / 10000);

        //实际占用CPU时间(=内核耗时+用户耗时)
        bigTmp2.HighPart = tmUser.dwHighDateTime;
        bigTmp2.LowPart = tmUser.dwLowDateTime;
        _tprintf(_T("t实际占用CPU时间:%I64dmsn"),
                 (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来计算线程时间

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

#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("线程正在运行中,请按任意键结束!n"));
    _gettchar();

    //通知线程结束
    SetEvent(hEvent);
    WaitForSingleObject(hThread, INFINITE);

    ULONG64 threadCircleTime;
    QueryThreadCycleTime(hThread, &threadCircleTime);
    DWORD dwElapseTime;
    GetExitCodeThread(hThread, &dwElapseTime);

    LARGE_INTEGER liFreq;

    QueryPerformanceFrequency(&liFreq);

    _tprintf(_T("n运行信息(TSC法):n"));
    _tprintf(_T("tCPU主频 :%dMHzn"), dwCPUFreq);
    _tprintf(_T("t晶振频率:%dMHzn"), liFreq.QuadPart / 1000);
    _tprintf(_T("t线程存活时间为:%dms(%dμs)n"), dwElapseTime / 1000, dwElapseTime);
    _tprintf(_T("t线程占用的CPU计数:%I64d(次)n"), threadCircleTime);
    _tprintf(_T("t线程占用的CPU时间:%I64d(μs)n"), 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("n运行信息(GetThreadTimes法):n"));

    bigTmp1.HighPart = tmCreation.dwHighDateTime;
    bigTmp1.LowPart = tmCreation.dwLowDateTime;
    bigTmp2.HighPart = tmExit.dwHighDateTime;
    bigTmp2.LowPart = tmExit.dwLowDateTime;
    _tprintf(_T("t线程存活时间:%I64dmsn"),
             (bigTmp2.QuadPart - bigTmp1.QuadPart) / 10000);

    bigTmp1.HighPart = tmKernel.dwHighDateTime;
    bigTmp1.LowPart = tmKernel.dwLowDateTime;
    bigTmp2.HighPart = tmUser.dwHighDateTime;
    bigTmp2.LowPart = tmUser.dwLowDateTime;
    _tprintf(_T("t线程占用的CPU时间:%I64d(μs)n"),
             (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

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/394695

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午3:52
下一篇 2023年4月3日 下午3:53

相关推荐