题目:写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率。程序越精简越好,计算机语言不限。例如,可以实现下面三种情况:

1.    CPU的占用率固定在50%,为一条直线;

2.    CPU的占用率为一条直线,但是具体占用率由命令行参数决定(参数范围1~ 100);

3.    CPU的占用率状态是一个正弦曲线。

首先什么是CPU占用率?

在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也就是说,任务管理器中显示的是每个刷新周期内CPU占用率的统计平均值。

因此可以写个程序,在一个刷新周期中,一会儿忙,一会儿闲,调节忙/闲比例,就可以控制CPU占有率!

一个刷新时间到底是多长,书上只是说,任务管理器观测,大约是1秒。鼠标移动、后台程序等都会对曲线造成影响!

单核环境下,空死循环会导致100%的CPU占有率。双核环境下,CPU总占有率大约为50%,四核会不会是25%左右呢?(没试过)

解法一:简单解法

Busy用可循环来实现,for(i=0;i<n;i++) ;

对应的汇编语言为

loop;

mov dx i     ;将i置入dx寄存器

inc dx          ;将dx寄存器加1

mov dx i       ;将dx中的值赋回i

cmp i n         ;比较i和n

j1 loop          ;i小于n时则重复循环

我的cpu(双核) 2.53GHZ 现代cpu每个时钟周期可执行两条以上的代码,取平均值2,于是

(2520 000 000*2)/5=1012000000(循环/秒) 每秒可以执行循环1012000000次

不能简单的取n=10120000000然后sleep(1000),如果让cpu工作1s,休息1s很可能是锯齿,先达到一个峰值然后跌入一个很低的占有率;所以我们睡眠时间改为10ms,10ms比较接近windows的调度时间,n=10120000。如果sleep时间选的太小,会造成线程频繁的唤醒和挂起,无形中增加了内核时间的不确定性因此代码如下:

[cpp] view plaincopy
  1. #include <windows.h>
  2. int main(void)
  3. {
  4. //50%
  5. //Thread 0 can only run on CPU 0.
  6. SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
  7. while(true)
  8. {
  9. for(int i=0;i<10120000;i++)
  10. ;
  11. Sleep(10);
  12. }
  13. return 0;
  14. }

自己的截图为: 有锯齿,近似直线吧。。。

解法二:使用GetTickCount()和Sleep()

GetTickCount()可以得到“系统启动到现在”所经历的时间的毫秒值,最多可以统计49.7天,因此我们可以利用GetTickCount()判断busy loop要循环多久,如下:

[cpp] view plaincopy
  1. #include <windows.h>
  2. int main(void)
  3. {
  4. //50%
  5. int busyTime=10;
  6. int idleTime=busyTime;
  7. _int64 startTime;
  8. SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
  9. while(true)
  10. {
  11. startTime=GetTickCount();
  12. while((GetTickCount()-startTime)<=busyTime)
  13. {
  14. ;
  15. }
  16. Sleep(idleTime);
  17. }
  18. return 0;
  19. }

截图为: 和解法一结果近似

上面两种解法都是假设当前系统只有当前程序在运行,但实际上,操作系统有很多程序会同时调试执行各种任务,如果此刻进程使用10%的cpu,那我们的程序只有使用40%的cpu才能达到50%的效果。

Perfmon.exe是从WIN NT开始就包含在windows管理工具中的专业检测工具之一。我们可以用程序来查询Perfmon的值,.Net Framework提供了PerformanceCounter这一对象,可以方便的查询当前各种性能数据,包括cpu使用率,因此解法三如下:

解法三:能动态适应的解法

[c-sharp] view plaincopy
  1. using System;
  2. using System.Diagnostics;
  3. namespace cpu
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. cpu(0.5);
  10. }
  11. static void cpu(double level)
  12. {
  13. PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");
  14. if (p == null)
  15. {
  16. return;
  17. }
  18. while (true)
  19. {
  20. if (p.NextValue() > level)
  21. System.Threading.Thread.Sleep(10);
  22. }
  23. }
  24. }
  25. }

结果: 在第二个cpu上?。。。

解法四:正弦曲线

[cpp] view plaincopy
  1. #include <windows.h>
  2. #include <math.h>
  3. int main(void)
  4. {
  5. SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
  6. const double SPLIT=0.01;
  7. const int COUNT=200;
  8. const double PI=3.14159265;
  9. const int INTERVAL=300;
  10. DWORD busySpan[COUNT]; //array of busy time
  11. DWORD idleSpan[COUNT]; //array of idle time
  12. int half=INTERVAL/2;
  13. double radian=0.0;
  14. for(int i=0;i<COUNT;i++)
  15. {
  16. busySpan[i]=(DWORD)(half+(sin(PI*radian)*half));
  17. idleSpan[i]=INTERVAL-busySpan[i];
  18. radian+=SPLIT;
  19. }
  20. DWORD startTime=0;
  21. int j=0;
  22. while(true)
  23. {
  24. j=j%COUNT;
  25. startTime=GetTickCount();
  26. while((GetTickCount()-startTime)<=busySpan[j])
  27. ;
  28. Sleep(idleSpan[j]);
  29. j++;
  30. }
  31. return 0;
  32. }

其中busySpan[i]=(DWORD)(half+(sin(PI*radian)*half));
  idleSpan[i]=INTERVAL-busySpan[i];

这样保证了占有率=busy/(busy+idle)=(half+(sin(PI*radian)*half))/(2*half)=(1+sin(PI*radian))/2 在0到100%之间!。

结果截图:依然锯齿 近似正弦曲线吧。。。

下面更深入讨论,http://www.cnblogs.com/flyinghearts/archive/2011/03/22/1991965.html这篇博文很好,讨论了在双核情况下,每个cpu显示不同的曲线。如下:

控制CPU占用率,不仅仅是出于好玩而已。以前的某些程序,特别是某些老游戏,在新的机器上运行速度太快,必须先给CPU降速,才能顺利运行那些程序,有个共享软件CPUKiller,就是专门弄这个的。

控制CPU占用率,因为要调用Windows的API,要考虑到多核、超线程的情况,要考虑到不同版本的Windows的计时相关的API的精度不同,使问题变得相当复杂,若再考虑其它程序的CPU占用率,则该问题则变得很烦人。(taskmgr调用了一个未公开的API)。

对CPU核数的判断,书上是调用GetProcessorInfo,其实可以直接调用GetSystemInfo,SYSTEM_INFO结构的dwNumberOfProcessors成员就是核数。不知道超线程对这两种方法有什么影响。

如果不考虑其它程序的CPU占用情况,可以在每个核上开一个线程,运行指定的函数,实现每个核的CPU占用率相同

要让CPU的占用率,呈函数 y = calc(t) (0 <= y <= 1, t为时间,单位为ms )分布,只要取间隔很短的一系列点,认为在某个间隔内,y值近似不变。

设间隔值为GAP,显然在指定t值附近的GAP这段时间内,

CPU占用时间为:busy = GAP * calc(t),

CPU空闲时间为:idle = GAP - busy

因此,很容易写出下面这个通用函数:

[cpp] view plaincopy
  1. void solve(Func *calc)
  2. {
  3. double tb = 0;
  4. while(1)
  5. {
  6. unsigned ta = get_time();
  7. double r = calc(tb);
  8. if (r < 0 || r > 1) r = 1;
  9. DWORD busy = r * GAP;
  10. while(get_time() - ta < busy) {}
  11. Sleep(GAP - busy);
  12. tb += GAP;
  13. }
  14. }

如果CPU占用率曲线不是周期性变化,就要对每个t值都要计算一次,否则,可以只计算第一个周期内的各个t值,其它周期的直接取缓存计算结果。

以CPU占用率为正弦曲线为例,显然:y = 0.5 * (1 + sin(a * t + b))

其周期T = 2 * PI / a  (PI = 3.1415927),可以指定T值为60s即60000ms,则

可以确定a值为 2 * PI / T, 若在这60000ms内我们计算200次(c = 200),则GAP值为 T / c = 300ms.也就是说,只要确定了周期和计算次数,其它几个参数也都确定下来。

代码如下

[cpp] view plaincopy
  1. #include<iostream>
  2. #include<cmath>
  3. #include<windows.h>
  4. static int PERIOD = 60 * 1000; //周期ms
  5. const int COUNT = 300;  //一个周期计算次数
  6. const double GAP_LINEAR = 100;  //线性函数时间间隔100ms
  7. const double PI = 3.1415926535898; //PI
  8. const double GAP = (double)PERIOD / COUNT; //周期函数时间间隔
  9. const double FACTOR = 2 * PI / PERIOD;  //周期函数的系数
  10. static double Ratio = 0.5;  //线性函数的值 0.5即50%
  11. static double Max=0.9; //方波函数的最大值
  12. static double Min=0.1; //方波函数的最小值
  13. typedef double Func(double);  //定义一个函数类型 Func*为函数指针
  14. typedef void Solve(Func *calc);//定义函数类型,参数为函数指针Func*
  15. inline DWORD get_time()
  16. {
  17. return GetTickCount(); //操作系统启动到现在所经过的时间ms
  18. }
  19. double calc_sin(double x)  //调用周期函数solve_period的参数
  20. {
  21. return (1 + sin(FACTOR * x)) / 2; //y=1/2(1+sin(a*x))
  22. }
  23. double calc_fangbo(double x)  //调用周期函数solve_period的参数
  24. {
  25. //方波函数
  26. if(x<=PERIOD/2) return Max;
  27. else return Min;
  28. }
  29. void solve_period(Func *calc) //线程函数为周期函数
  30. {
  31. double x = 0.0;
  32. double cache[COUNT];
  33. for (int i = 0; i < COUNT; ++i, x += GAP)
  34. cache[i] = calc(x);
  35. int count = 0;
  36. while(1)
  37. {
  38. unsigned ta = get_time();
  39. if (count >= COUNT) count = 0;
  40. double r = cache[count++];
  41. DWORD busy = r * GAP;
  42. while(get_time() - ta < busy) {}
  43. Sleep(GAP - busy);
  44. }
  45. }
  46. void solve_linear(Func*)  //线程函数为线性函数,参数为空 NULL
  47. {
  48. const unsigned BUSY =  Ratio * GAP_LINEAR;
  49. const unsigned IDLE = (1 - Ratio) * GAP_LINEAR;
  50. while(1)
  51. {
  52. unsigned ta = get_time();
  53. while(get_time() - ta < BUSY) {}
  54. Sleep(IDLE);
  55. }
  56. }
  57. //void solve_nonperiod(Func *calc) //非周期函数的处理,暂没实验
  58. //{
  59. //  double tb = 0;
  60. //  while(1)
  61. //  {
  62. //    unsigned ta = get_time();
  63. //    double r = calc(tb);
  64. //    if (r < 0 || r > 1) r = 1;
  65. //    DWORD busy = r * GAP;
  66. //    while(get_time() - ta < busy) {}
  67. //    Sleep(GAP - busy);
  68. //    //tb += GAP;
  69. //    tb += get_time() - ta;
  70. //  }
  71. //}
  72. void run(int i=1,double R=0.5,double T=60000,double max=0.9,double min=0.1)
  73. //i为输出状态,R为直线函数的值,T为周期函数的周期,max方波最大值,min方波最小值
  74. {
  75. Ratio=R; PERIOD=T; Max=max; Min=min;
  76. Func *func[] = {NULL ,calc_sin,calc_fangbo};  //传给Solve的参数,函数指针数组
  77. Solve *solve_func[] = { solve_linear, solve_period};  //Solve函数指针数组
  78. const int NUM_CPUS = 2;  //双核,通用的可以用下面GetSystemInfo得到cpu数目
  79. HANDLE handle[NUM_CPUS];
  80. DWORD thread_id[NUM_CPUS]; //线程id
  81. //SYSTEM_INFO info;
  82. //GetSystemInfo(&info);   //得到cpu数目
  83. //const int num = info.dwNumberOfProcessors;
  84. switch(i)
  85. {
  86. case 1: //cpu1 ,cpu2都输出直线
  87. {
  88. for (int i = 0; i < NUM_CPUS; ++i)
  89. {
  90. Func *calc = func[0];
  91. Solve *solve = solve_func[0];
  92. if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
  93. (VOID*)calc, 0, &thread_id[i])) != NULL)  //创建新线程
  94. SetThreadAffinityMask(handle[i], i+1); //限定线程运行在哪个cpu上
  95. }
  96. WaitForSingleObject(handle[0],INFINITE);   //等待线程结束
  97. break;
  98. }
  99. case 2: //cpu1直线,cpu2正弦
  100. {
  101. for (int i = 0; i < NUM_CPUS; ++i)
  102. {
  103. Func *calc = func[i];
  104. Solve *solve = solve_func[i];
  105. if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
  106. (VOID*)calc, 0, &thread_id[i])) != NULL)  //创建新线程
  107. SetThreadAffinityMask(handle[i], i+1); //限定线程运行在哪个cpu上
  108. }
  109. WaitForSingleObject(handle[0],INFINITE);   //等待线程结束
  110. break;
  111. }
  112. case 3: //cpu1直线,cpu2方波
  113. {
  114. /*Func *calc = func[0];
  115. Solve *solve = solve_func[0];*/
  116. if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0],
  117. (VOID*)func[0], 0, &thread_id[0])) != NULL)  //创建新线程
  118. SetThreadAffinityMask(handle[0], 1); //限定线程运行在哪个cpu上
  119. Func *calc = func[2];
  120. Solve *solve = solve_func[1];
  121. if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
  122. (VOID*)calc, 0, &thread_id[1])) != NULL)  //创建新线程
  123. SetThreadAffinityMask(handle[1], 2); //限定线程运行在哪个cpu上
  124. WaitForSingleObject(handle[0],INFINITE);   //等待线程结束
  125. break;
  126. }
  127. case 4: //cpu1正弦,cpu2方波
  128. {
  129. if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[1],
  130. (VOID*)func[1], 0, &thread_id[0])) != NULL)  //创建新线程
  131. SetThreadAffinityMask(handle[0], 1); //限定线程运行在哪个cpu上
  132. Func *calc = func[2];
  133. Solve *solve = solve_func[1];
  134. if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,
  135. (VOID*)calc, 0, &thread_id[1])) != NULL)  //创建新线程
  136. SetThreadAffinityMask(handle[1], 2); //限定线程运行在哪个cpu上
  137. WaitForSingleObject(handle[0],INFINITE);   //等待线程结束
  138. break;
  139. }
  140. default: break;
  141. }
  142. }
  143. void main()
  144. {
  145. run(1,0.5);  //cpu1 ,cpu2都输出50%的直线
  146. //run(2,0.5,30000); //cpu1 0.5直线,cpu2正弦周期30000
  147. //run(3);  //cpu1直线,cpu2方波
  148. //run(4,0.8,30000,0.95,0.5); //cpu1正弦,cpu2 0.95-0.5的方波
  149. }

结果如下(双核)

run(1,0.5):

run(2,0.5,30000):

run(3):


run(4,0.8,30000,0.95,0.5):

结束语:蛋疼时候可以拿任务管理器当示波器玩。。。

补充几个函数的说明:

GetTickCount返回(retrieve)从操作系统启动到现在所经过(elapsed)的毫秒数,它的返回值是DWORD。函数原型:   DWORD GetTickCount(void); 
C/C++头文件:winbase.h ;windows程序设计中可以使用头文件windows.h

Sleep()函数
C++中头文件<windows.h>下的函数 作用:延时,程序暂停若干时间。时间,就是他的参数,单位是毫秒。Sleep (500) ; //注意第一个字母是大写。就是到这里停半秒,然后继续向下执行。  在Linux C语言中 sleep的单位是秒 sleep(5); //停5秒 包含在 <unistd.h>头文件中

CreateThread,建立新的线程
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);
概述:
当使用CreateProcess调用时,系统将创建一个进程和一个主线程。CreateThread将在主线程的基础上创建一个新线程,大致做如下步骤:
1在内核对象中分配一个线程标识/句柄,可供管理,由CreateThread返回
2把线程退出码置为STILL_ACTIVE,把线程挂起计数置1
3分配context结构
4分配两页的物理存储以准备栈,保护页设置为PAGE_READWRITE,第2页设为PAGE_GUARD
5lpStartAddr和lpvThread值被放在栈顶,使它们成为传送给StartOfThread的参数
6把context结构的栈指针指向栈顶(第5步)指令指针指向startOfThread函数
语法:
hThread = CreateThread (&security_attributes,dwStackSize,ThreadProc,pParam,dwFlags, &idThread) ; 
参数说明:  
    第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。
  CreateThread的第三个参数是指向线程函数的指针。函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI ThreadProc (PVOID pParam) ; 
 CreateThread的第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。
  CreateThread的第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。第六个参数是一个指标,指向接受执行绪ID值的变量。

SetThreadAffinityMask:The SetThreadAffinityMask function sets a processor affinity mask for the specified thread.
DWORD_PTR SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffinityMask);
调用SetThreadAffinityMask,能为各个线程设置亲缘性屏蔽:该函数中的h T h r e a d参数用于指明要限制哪个线程, dwThreadAffinityMask用于指明该线程能够在哪个CPU上运行。dwThreadAffinityMask必须是进程的亲缘性屏蔽的相应子集。返回值是线程的前一个亲缘性屏蔽。因此,若要将3个线程限制到CPU1、2和3上去运行,可以这样操作:
//Thread 0 can only run on CPU 0.
SetThreadAffinityMask(hThread0, 0x00000001); //第0位是1
//Threads 1, 2, 3 run on CPUs 1, 2, 3.//第1 2 3位是1
SetThreadAffinityMask(hThread1, 0x0000000E);
SetThreadAffinityMask(hThread2, 0x0000000E);
SetThreadAffinityMask(hThread3, 0x0000000E);

WaitForSingleObject   当指定的对象的状态被标记或者指定的时间间隔过完时,此函数返回DWORD类型参数。
格式:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
参数:hHandle表示对象的句柄,dwMilliseconds指出了时间间隔;过了指定的时间,即使对象状态没发生改变,函数也会返回;如果此参数设为0,函数测试对象的状态并且立即返回;如果此参数设为INFINITE,则表示此函数的时间间隔永远不会流逝完——只有等待对象状态被标识时返回。
返回值:成功:WAIT_OBJECT_0:表示对象的状态被标识
         WAIT_TIMEOUT:表示指定时间已到而对象状态没有被标识
    失败:WAIT_FAILED:表明失败
WaitForSingleObject 函数检查指定对象当前状态,如果对象的状态没有被标识,则调用的线程进入有效的等待状态。在等待对象状态被标识或者指定的时间间隔到期,线程只会占据(consume)处理器一小段时间。时间间隔需要被指定在0到0x7FFFFFFF之间的正数,最大的时间间隔值不等于无穷大而是0x7FFFFFFF,无穷大的时间间隔值是0xFFFFFFFF。任何在0x7FFFFFFF和0xFFFFFFFE之间的值都等同于0x7FFFFFFF;如果你需要一个时间间隔比0x7FFFFFFF还要大的话,使用表示不穷的值0xFFFFFFFF。返回之前,等待函数修改了某些类型的同步对象的状态,只有当对象的信号状态引起了函数的返回时这种修改才发生。例如,一个信号量对象计数减少1。WaitForSingleObject 函数能等待如下的各种对象:事件(Event)、线程(Thread)、进程(Process)、互斥量(Mutex)、信号量(Semaphore)。
使用时要小心调用等待函数和直接或间接产生窗口的代码。如果一个线程创建了窗口,那么它必须处理消息。广播消息发送到系统中的所有窗口。使用一个没有时间间隔的等待函数的线程可能导致系统死锁。例如,动态数据交换(DDE)协议和COM函数CoInitialize两个都间接地创建了可能导致死锁的窗口。因此,如果您有一个线程创建的窗口,使用MsgWaitForMultipleObjects 或者 MsgWaitForMultipleObjectsEx 而不是使用WaitForSingleObject。

======================================END===================================

(1.5.1.1)编程之美:让CPU占用率曲线听你指挥相关推荐

  1. 让多核CPU占用率曲线听你指挥(Windows实现)——《编程之美》1.1学习笔记

    让多核CPU占用率曲线听你指挥--<编程之美>1.1学习笔记 Problem: 写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率.有以下几种情况: ...

  2. 编程之美:让CPU占用率曲线听你指挥

    题目:写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率.程序越精简越好,计算机语言不限.例如,可以实现下面三种情况: 1.    CPU的占用率固定在50%, ...

  3. 《编程之美》读书笔记23: 1.1 让CPU占用率曲线听你指挥

    题目:写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率.程序越精简越好,计算机语言不限.例如,可以实现下面三种情况:   1.    CPU的占用率固定在50 ...

  4. 编程之美读书笔记_1.1_让CPU占用率曲线听你指挥

    题目:写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率.程序越精简越好,计算机语言不限.例如,可以实现下面三种情况:   1.    CPU的占用率固定在50 ...

  5. 编程之美-控制CPU占用率曲线方法整理

    [试题描述] 方法一:简单的解法 方法二:使用GetTickCount()和Sleep() 方法三:你能动态适应的解法 方法四:正弦曲线 方法五: 附:一些与线程/进程/系统效能相关的API

  6. 编程之美2.10:寻找数组中的最大值和最小值

    编程之美2.10: 对于一个有N个整数组成的数组,需要比较多少次才能把最大值和最小值找出来呢? 算法的思想是: 分而治之 测试数据:---------------------------------- ...

  7. 编程之美2.1 求二进制中1的个数

    最近一段的时间,一直在看编程之美之类的算法书籍,刚开始看编程之美,感觉到难度太大,有时候也不愿意去翻动这本书,不过,经过一段时间的修炼,我也彻底的喜欢上这本书了, 书中的算法涉及到很多方面,树,链表, ...

  8. 2017“编程之美”终章:AI之战勇者为王

    编者按:8月15日,第六届微软"编程之美"挑战赛在选手的火热比拼中圆满落下帷幕."编程之美"挑战赛是由微软主办,面向高校学生开展的大型编程比赛.自2012年起, ...

  9. Java 并发编程之美:并发编程高级篇之一-chat

    借用 Java 并发编程实践中的话:编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作 ...

最新文章

  1. JSP Problem(第一次创建Web Project报错)
  2. JQuery中text(),html(),val()的区别
  3. 将指标标签与MicroProfile Metrics 2.0结合使用
  4. 2019年技术盘点云数据库篇(一):UCloud专家谈云数据库:千锤百炼 云之重器
  5. Tomcat服务器安装PFX格式证书
  6. python 之 ------- 协程(微线程)
  7. (13)FPGA设计思想
  8. NoticeBoard 一个仿原生UI的消息通知控件
  9. 【问题解决方案】anaconda-python在cmd-pip安装requests后依然提示No module named requests
  10. 《游戏设计师修炼之道:数据驱动的游戏设计》一3.2 漏洞管理计划和技术开发...
  11. 原生android字体,安卓原生字体
  12. jquery ui php,推荐10款最热门jQuery UI框架[原创]_jquery
  13. SCPM供应链管理专业证书知多少
  14. VM16 安装win 11
  15. linux修改用户uid gid
  16. 2021年茶艺师(中级)考试及茶艺师(中级)实操考试视频
  17. Django自定义User模型以及用户系统(用户、权限、组)的使用
  18. RN iOS 真机器调试
  19. 神经网络中的对抗样本
  20. 对SG函数(Sprague-Garundy函数)及其应用的简单解释与证明

热门文章

  1. python程序计时_Python计时器小程序
  2. C语言400行实现文本编辑器
  3. 无人智能配送机器人现状
  4. CP2102 USB to UART Bridge Controller 串口驱动安装(windows or Ubuntu)
  5. 传智播客软件测试-在路上
  6. 电场和磁场在交界面上的连续性
  7. wx朋友圈Hook(一)
  8. 图像处理之图像变换(放缩、平移、旋转、仿射变换、透视变换)
  9. Matlab之图像变换技术(十二)
  10. FL2440(S3C2440A 芯片) 开发板开发笔记