0%

线程相关操作

线程操作

回过头来再看一下这个东西,还是有那么几点是需要注意的。

线程优先级

就是线程的一个操作级别,分为很多种,高级别的先运行低级别的后运行(其实有32个,但是Windows提供的接口就那么几个)。
SetThreadPriority通过这个函数就可以设置。
看下面的操作实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <windows.h>

DWORD WINAPI ThreadIdle(LPVOID l)
{
int i=0;
while(i++<10)
printf("Idel\n");
return 0;
}
DWORD WINAPI ThreadNormal(LPVOID l)
{
int i=0;
while(i++<10)
printf("Normal\n");
return 0;
}

int main(int argc, TCHAR* argv[])
{
DWORD dwThreadID;
HANDLE h[2];
h[0] = CreateThread(NULL,
0,
ThreadIdle,
NULL,
CREATE_SUSPENDED,
&dwThreadID);
SetThreadPriority(h[0],THREAD_PRIORITY_IDLE);
ResumeThread(h[0]);
h[1] = CreateThread(NULL,
0,
ThreadNormal,
NULL,
0,
&dwThreadID);
WaitForMultipleObjects(2,h,TRUE,INFINITE);
CloseHandle(h[0]);
CloseHandle(h[1]);
return 0;
}

比较简单,就是开两个线程,来看运行的先后顺序,先暂停第一个低级别,设置完级别之后就开第二个中级别的,最后等内核对象执行完释放。
但是可能我的CPU比较强大(嘻嘻嘻,12个肯定快喽),所以我这里基本就是间接运行的,在低版本XP的时候还是可以看出效果的,我这里的效果是:
效果图
可以看到Normal虽然在后面,但是还是有重叠,但是不稳定。

临界区对象

这个之前说过,而且操作起来相对简单,这里就不提了。
但有一点需要注意的是,这个临界区不是内核对象,所以不能再进程之间进行同步。

互锁函数

这个操作也是比较简单,但是就是单一了,只能对变量进行加减:
减:

1
2
3
LONG InterlockedDecrement(
LPLONG lpAddend // address of the variable to decrement
);

加:

1
2
3
LONG InterlockedIncrement(
LPLONG lpAddend // pointer to the variable to increment
);

提供参数给他就能保证安全了。

内核事件对象

这个算是比较常用的,主要是有两点比较混淆。
分为自动重置和人工重置,区分就是:

  1. 自动重置的话呢,当我们的函数执行完毕没必要调用ResetEvent这个函数告诉其他线程,并且当我们执行完毕之后只有一个等待的对象会进行工作。
  2. 人工重置的话呢就是,执行完毕我们还要调用ResetEvent来让其他线程知道,但是这样会让所有线程都活过来。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <windows.h>
#include <process.h>
HANDLE g_hEvent;
UINT __stdcall ChildFunc(LPVOID);
int main(int argc, TCHAR* argv[])
{
HANDLE hChildThread;
UINT uId;
g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
hChildThread = (HANDLE)_beginthreadex(NULL,0,ChildFunc,NULL,0,&uId);
printf("Please sure to working:");
getchar();
SetEvent(g_hEvent);
WaitForSingleObject(hChildThread,INFINITE);
printf("end");
CloseHandle(hChildThread);
CloseHandle(g_hEvent);
return 0;
}

UINT __stdcall ChildFunc(LPVOID)
{
WaitForSingleObject(g_hEvent,INFINITE);
printf("Working\n");
Sleep(5000);
return 1;
}

TLS局部存储

这个东西是非常的好用滴,就是说每个线程都有属于自己的一块线程位数组,每个位都是存放一个索引的,通过一个位可以存取和放置一个LPVOID的值,这个位是由主线程提供的。
简单地说就是主线程提供一个索引,子线程通过这个索引可以存取和获取这个索引对应的值,并且这个是独立,一个线程是没办法设置别的线程对应的值的。
看下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ThreadTime.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <stdio.h>
#include <windows.h>
#include <process.h>

DWORD g_tlsUsedTime;
void InitStartTime();
DWORD GetUsedTime();

UINT __stdcall ChildFunc(LPVOID);
int _tmain(int argc, TCHAR* argv[])
{
UINT uId;
int i;
HANDLE h[10];
g_tlsUsedTime = TlsAlloc();

for(i = 0;i<10;i++)
h[i] = (HANDLE)_beginthreadex(NULL,0,ChildFunc,NULL,0,&uId);
WaitForMultipleObjects(10,h,TRUE,INFINITE);

for(i = 0;i<10;i++)
CloseHandle(h[i]);


TlsFree(g_tlsUsedTime);

system("pause");
return 0;
}

UINT __stdcall ChildFunc(LPVOID)
{
int i;
InitStartTime();
i = 10000*10000;
while(i--){};
printf("ProcessID:%d,Used Time:%d\n",GetCurrentThreadId(),GetUsedTime());
return 0;
}

void InitStartTime()
{
DWORD dwStart = GetTickCount();
TlsSetValue(g_tlsUsedTime,(LPVOID)dwStart);
}
DWORD GetUsedTime()
{
DWORD dwEnd = GetTickCount();
dwEnd = dwEnd-(DWORD)TlsGetValue(g_tlsUsedTime);
return dwEnd;
}

这个就是获取时间的一个验证程序。
效果
思路比较简单,就是通过设置这个独特的位置开始的时间,然后运行完毕之后,再通过获取这个位来达到计时的效果。

简单的总结这个线程操作,就是公共的变量我们必须要保护起来,因为你不知道什么时候他就会出现错误,可能变成别的值,因为CPU是靠时间片来运行的,突然分给别的线程是很有可能的。