您查询的关键词是:lpstartaddress 。如果打开速度慢,可以尝试快速版;如果想保存快照,可以添加到搜藏。 (百度和网页http://bbs.288888.org/bbs/archiver/?tid-7922.html的作者无关,不对其内容负责。百度快照谨为网络故障时之索引,不代表被搜索网站的即时页面。) |
进程和线程,及Visual C++多线程开发
写在前,下面是我读Windows SDK的一些笔记,希望能与需要它的人一起分享。其中有些部分还未读完,我会尽快完善。1创建线程
可用CreateThread创建一个线程,创建线程的时候必须指明新线程的执行地址,通常它的执行地址就是在程序代码内定义的函数的名称,这个函数只有一个参数,返回的是一个DWORD类型的值。一个进程可以有多个线程同时在执行相同的函数。
如果主建线程在新线程(子线程)前退出程序,那么传地址的方式传递局部变量给新线程就会存在风险,因为指针会变成无效的,所以必须等新线程退出后才能退出子线程。也可以用全局变量来传参数给新线程,但是这就要注意多线程的同步访问。但有的时候,在创建多线程时使用全局变量将不太方便。
对于CreateThread函数的参数,主建线程可以使用的参数有如下几个:
第一个:新线程的安全属性(securityattributes)。该属性表明一个子进程是否能够继承该新线程。安全属性也被系统用来在允许访问之前检查所有对这个线程的并发访问。
第二个:新线程的初始栈空间(initialstack size)大小。线程的栈被自动分配在进程的内存空间内,系统在线程被创建时分配栈并在线程结束时释放栈。
第三个:创建标识(creationflag)使你在挂起状态下创建这个线程。挂起后,直到ResumeThread函数被调用后线程才会被调用。
你也可以用CreateRemoteThread函数来创建一个线程。不过这个函数多用于调试进程在被调试的进程的地址空间内创建一个运行在该空间内的线程。
(2005-4-12)
2挂起线程的执行
通过使用SuspendThread和ResumeThread函数可以将一个线程挂起和激活执行。当一个线程被挂起后,处理器将不为它分配预定的时间。SuspendThread函数不适合用于线程的同步,因为它无法控制代码内,线程的执行在哪一点被挂起。
3多个线程的同步执行
为了避免竞争和死锁,多线程对共享资源的同步访问是有必要的。同步还确保相互以来的代码以合适的顺序执行。
有很多的对象可以用于同步多线程。这些对象包括:
控制输入缓冲区
<!--[if !supportLists]-->事件
<!--[if !supportLists]--><!--[endif]-->互斥
<!--[if !supportLists]--> 进程
<!--[if !supportLists]--> <!--[endif]-->信号量
<!--[if !supportLists]-->线程
<!--[if !supportLists]-->计时器
这些对象中的一些常用于阻塞一个线程直到某个事件发生。例如,
其他的对象对于保护共享资源不被同时访问是非常有用的。例如,
对于一个单进程的多个线程,临界区(critical-section)对象提供的同步方法比互斥对象更有效。临界区可以像互斥对象一样,使得在某一个时间只有一个线程去使用受保护的资源。一个线程可以调用EnterCriticalSection函数来请求某个临界区的所有权。如果它正在被另一个线程使用,那么这个发起请求的线程被阻塞。一个线程也可以调用TryEnterCriticalSection函数来请求某个临界区的所有权,此时即使请求失败也不会被阻塞。在获得临界区所有权后,线程可以自由地使用受保护资源。该进程的其它线程如果不试图进入临界区,它们的执行就不会受到影响。
WaitForInputIdle函数使一个线程等待直到一个指定的进程被初始化并且等待用户输入一个确定的输入。调用WaitForInputIdle函数可以被用于同步的父进程和子进程,因为CreateProcess函数不用等到子进程完成初始化才返回。
4线程的局部存储
5中止一个线程
6线程安全和访问权限
Windows NT安全模式使你可以控制对线程对象的访问。当你调用CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW,CreateThread, 或者CreateRemoteThread函数时,你可以为一个线程指定一个安全描述符(securitydescriptor)。如果你指定为NULL,该线程将得到一个默认的安全描述符。线程中的默认安全描述符的ACL是主要的或者是像创建者的标识(token)一样重要。
(2005-4-13)
GetSecurityInfo函数可用来获得某个线程的安全描述符,SetSecurityInfo函数可用来改变某个线程的安全描述符。
由CreateThread函数返回的句柄拥有THREAD_ALL_ACCESS的权限来访问那个线程对象。当你调用GetCurrentThread函数时,系统返回一个安全描述符允许的具有最小访问权限的伪句柄给调用者。
对线程对象合法的访问权限包括DELETE,READ_CONTROL,SYNCHRONIZE,WRITE_DAC,and WRITE_OWNER这些标准访问权限,此外,下面的是一些线程的特殊访问权限:
值
含义
SYNCHRONIZE
可以在任何的等待函数中使用线程句柄
THREAD_ALL_ACCESS
线程对象的所有可能访问权限
THREAD_DIRECT_IMPERSONATION
一个模仿客户的服务器线程所必须的
THREAD_GET_CONTEXT
使用GetThreadConttext读取某个线程的上下文时所必须的
THREAD_IMPERSONATE
Required to use a thread's security information directly without calling it by using a communication mechanism that provides impersonation services.
THREAD_QUERY_INFORMATION
从某个线程读取确定信息(例如退出代码,参看GetExitCodeThread函数)所必须的。
THREAD_SET_CONTEXT
使用SetThreadContext写一个线程的上下文时所必须的
THREAD_SET_INFORMATION
在线程对象中设置确定的信息时所必须的
THREAD_SET_THREAD_TOKEN
Required to set the impersonation token for a thread using SetTokenInformation.
THREAD_SUSPEND_RESUME
挂起或激活一个线程时所必须的
THREAD_TERMINATE
使用TerminateThread函数中止一个线程时所必须的
如果想读或写一个线程对象的SACL(安全控制列表)时,你可以对该线程使用ACCESS_SYSTEM_SECURITY的访问权限。
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=348506[/url]
[收藏到我的网摘] Binary发表于 2005年04月15日 10:37:00
如何在VC中利用系统函数创建一个新的线程(菜鸟篇)
我们知道,要创建一个线程,必须得有一个主进程,然后由这个主进程来创建一个线程,在一般的VC程序中,主函数所在的进程就是程序的主进程。让我们从主函数来开始编写我们这个简单的小程序。我们知道CreateThread函数可以用来创建一个线程,在MSDN中查找这个函数得到如下信息:"The CreateThread function creates a thread to execute within the address space of the calling process."和"If the function succeeds, the return value is a handle to the new thread."所以我们得定义一个句柄用来存放它的返回值。还定义一个指向线程ID的DWORD值dwThreadId。然后我们就可以用CreateThread函数来创建我们的线程了,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
其中第一个参数我们设置为NULL,使这个句柄不能被继承;第二个参数设置为0,使用默认的堆栈大小;第三个参数为线程函数的起始地址,也就是线程函数的函数名;第四个参数为NULL,没有值要传递给线程函数;第五个参数为0,创建好之后马上让线程运行;第六个参数设置为指向线程ID的地址。创建好线程之后,线程函数进行初始化之类的操作,主函数继续执行,此时可以输出被创建线程的ID。我们在主函数中用WaitForSingleObject函数来等待线程函数变成受信(signaled)状态,它的两个参数分别是
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
第一参数为线程函数的句柄,第二个参数设置为INFINITE,等待线程一直执行完。在程序的最后还要记得用CloseHandle函数关闭线程,这样主函数就写完了。
在线程函数里面我们可以简单地做一些工作,比如设置一个循环,让它输出一定的信息等。源程序如下:
#include <windows.h>
#include <iostream.h>
DWORD WINAPI ThreadFunc(HANDLE Thread)
{
int i;
for(i=0;i<10;i++)
{
cout<<"A new thread has created!"<<endl;
}
return 0;
}
int main(int argc,char* argv[])
{
HANDLE Thread;
DWORD dwThreadId;
Thread=::CreateThread
(NULL,0,ThreadFunc,NULL,0,&dwThreadId);
cout<<"The new thread ID is :"<<dwThreadId<<endl;
::WaitForSingleObject(Thread,INFINITE);
::CloseHandle(Thread);
return 0;
}
在Window xp sp2&VC++ 6.0环境下编译通过。
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=616143[/url]
C++学习体会--在类中创建线程
这几天学习了一些线程函数的使用方法,我主要用到的是_beginthreadex,CreateMutex,ReleaseMutex,WaitForSingleObject函数1.将类的方法作为线程函数
不能将类的非静态方法做为线程函数,因为作为参数传进去时带有this指针.解决方法是将函数声明为static或友元函数,然后通过参数把类的对象传进去
2.在栈上创建的变量系统会自动回收,这样在创建线程类对象时一定要保证它不能先于线程执行完之前释放.如下面的代码是错误的
void threadTest(){
threadClass tc;
tc.startANewThread();
}
在函数hreadTest 调用结束之后tc将会被回收,导致内存问题.
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=793923[/url]
win32多线程学习笔记(一)
1:HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
A: 第三个参数是个函数指针,指向某种特定的函数,调用约定是WINAPI//#define WINAPI __stdcall,
参数LPVOID保障了函数的合法性.
B: 次函数两个得到两个值:第一个值是HANDLE,大部分和线程有关的API函数都要使用它;第二个是参数ThreadID带回来的值,它是独一无二的表示一个进程中的某个线程.
说明:我们不可以从一个线程的ID从而得到其HANDLE.
2: BOOL CloseHandle(
HANDLE hObject
);
用来释放核心对象。
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=275961[/url]
Windows多线程技术研究(三):线程API
介绍完线程的基本概念后,接下来描述在windows下如何创建线程,以及相应的使用多线程一些基本API。使用CreateThread()函数来创建一个线程。其函数原型为:
HANDLE CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAttribules,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParamenter,
DWORD dwCreationFlags,
LPDWORD lpThreadid
) ;
lpThreadAttribules – 新线程的serurity属性。NULL表示使用缺省值。
dwStackSize – 新线程堆栈的大小。0表示缺省大小:1MB。
lpStartAddress – 新线程开始地址。是一个函数指针,可以传入一个函数名。
lpParamenter – 新线程函数的参数。
dwCreationFlags – 创建标志。默认为立即执行。
lpThreadid – 新线程的ID。
CreateThread()传回来的handle被称为一个核心对象。当完成线程工作后,需要调用CloseHandle()函数来释放该核心对象。
Threads的行为很像中国人,如果让他们单兵作战,那么性能很好,效率也不错;但是一旦让他们合作,那各种麻烦、bug就会纷纷而至。所以你必须在使用时多加考虑。
由于当有多个线程执行时,这些线程在某一时刻谁执行都是不确定的。所以如果多个线程之间没有访问共享资源,并且不需要考虑所有线程都执行完或者某几个线程执行完时,那么多线程就变得很简单,创建完多个线程后放任其执行即可。但是一旦要访问共享资源或者需要等待多个线程执行完时,则需要一些其他处理。
互斥:
互斥是由于多个线程需要访问同一资源造成的,最简单的方法是临界区。设置一个CRITICAL_SECTION类型的变量,使用InitializeCriticalSection()来初始化该变量,然后在线程访问共享资源之前,使用EnterCriticalSection()来设置临界区,访问完之后再使用LeaveCriticalSection()来取消临界区。最后线程结束后调用DestroyCriticalSection()来删除该临界区变量。
等待:
很多情况下,需要让多个线程都执行完或者部分执行完后再做其他操作。使用WaitForSingleObject()和WaitForMultipleObjects()函数来进行等待。
DWORD WaitForSingleObject(
HANDLE hHandle, //线程句柄
DWORD dwMilliseconds //等待时间,INFINITE为等到线程结束
);
DWORD WaitForMultipleObjects(
DWORD nCount, //lpHandles指向的句柄个数
CONST HANDLE * lpHandles, //线程句柄数组
BOOL bWaitAll, //true等待句柄数组中的所有线程,false
//等待其中一个线程执行完
DWORD dwMilliseconds //等待时间,INFINITE为无限等待直至
//一个线程结束或所有线程结束
);
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=636409[/url]
线程知识点
线程的基础知识1. 进程与线程有那些区别和联系?
l 每个进程至少需要一个线程。
l 进程由两部分构成:进程内核对象,地址空间。线程也由两部分组成:线程内核对象,操作系统用它来对线程实施管理。线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。
l 进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。
l 如果在单进程环境中,有多个线程正在运行,那么这些线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。
l 进程使用的系统资源比线程多得多。实际上,线程只有一个内核对象和一个堆栈,保留的记录很少,因此需要很少的内存。因此始终都应该设法用增加线程来解决编程问题,避免创建新的进程。但是许多程序设计用多个进程来实现会更好些。
2. 如何使用_beginthreadex函数?
使用方法与CreateThread函数相同,只是调用参数类型需要转换。
3. 如何使用CreateThread函数?
当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。使用时应当注意在不需要对线程内核进行访问后调用CloseHandle函数关闭线程句柄。因为CreateThread函数中使用某些C/C++运行期库函数时会有内存泄漏,所以应当尽量避免使用。
参数 含义
lpThreadAttributes 如果传递NULL该线程使用默认安全属性。如果希望所有的子进程能够继承该线程对象的句柄,必须将它的bInheritHandle成员被初始化为TRUE。
dwStackSize 设定线程堆栈的地址空间。如果非0,函数将所有的存储器保留并分配给线程的堆栈。如果是0,CreateThread就保留一个区域,并且将链接程序嵌入.exe文件的/STACK链接程序开关信息指明的存储器容量分配给线程堆栈。
lpStartAddress 线程函数的地址。
lpParameter 传递给线程函数的参数。
dwCreationFlags 如果是0,线程创建后立即进行调度。如果是CREATE_SUSPENDED,系统对它进行初始化后暂停该线程的运行。
LpThreadId 用来存放系统分配给新线程的ID。
4. 如何终止线程的运行?
(1) 线程函数返回(最好使用这种方法)。
这是确保所有线程资源被正确地清除的唯一办法。
如果线程能够返回,就可以确保下列事项的实现:
•在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。
•操作系统将正确地释放线程堆栈使用的内存。
•系统将线程的退出代码设置为线程函数的返回值。
•系统将递减线程内核对象的使用计数。
(2) 调用ExitThread函数(最好不要使用这种方法)。
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。
(3) 调用TerminateThread函数(应该避免使用这种方法)。
TerminateThread能撤消任何线程。线程的内核对象的使用计数也被递减。TerminateThread函数是异步运行的函数。如果要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数。当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。
(4) 包含线程的进程终止运行(应该避免使用这种方法)。
由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。就像从每个剩余的线程调用TerminateThread一样。这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。
一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调用GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。
5. 为什么不要使用_beginthread函数和_endthread函数?
与_beginthreadex函数相比参数少,限制多。无法创建暂停的线程,无法取得线程ID。_endthread函数无参数,线程退出代码必须为0。还有_endthread函数内部关闭了线程的句柄,一旦退出将不能正确访问线程句柄。
6. 如何对进程或线程的内核进行引用?
HANDLE GetCurrentProcess( );
HANDLE GetCurrentThread( );
这两个函数都能返回调用线程的进程的伪句柄或线程内核对象的伪句柄。伪句柄只能在当前的进程或线程中使用,在其它线程或进程将不能访问。函数并不在创建进程的句柄表中创建新句柄。调用这些函数对进程或线程内核对象的使用计数没有任何影响。如果调用CloseHandle,将伪句柄作为参数来传递,那么CloseHandle就会忽略该函数的调用并返回FALSE。
DWORD GetCurrentProcessId( );
DWORD GetCurrentThreadId( );
这两个函数使得线程能够查询它的进程的唯一ID或它自己的唯一ID。
7. 如何将伪句柄转换为实句柄?
HANDLE hProcessFalse = NULL;
HANDLE hProcessTrue = NULL;
HANDLE hThreadFalse = NULL;
HANDLE hThreadTrue = NULL;
hProcessFalse = GetCurrentProcess( );
hThreadFalse = GetCurrentThread( );
取得线程实句柄:
DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse, &hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
取得进程实句柄:
DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse, &hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
由于DuplicateHandle会递增特定对象的使用计数,因此当完成对复制对象句柄的使用时,应该将目标句柄传递给CloseHandle,从而递减对象的使用计数。
8. 在一个进程中可创建线程的最大数是得多少?
线程的最大数取决于该系统的可用虚拟内存的大小。默认每个线程最多可拥有至多1MB大小的栈的空间。所以,至多可创建2028个线程。如果减少默认堆栈的大小,则可以创建更多的线程。
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=10998[/url]
线程的基础知识
1. 进程与线程有那些区别和联系?每个进程至少需要一个线程。
进程由两部分构成:进程内核对象,地址空间。线程也由两部分组成:线程内核对象,操作系统用它来对线程实施管理。线程堆栈,用于维护线程在执行代码时需要的所有函数参数和局部变量。
进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。
如果在单进程环境中,有多个线程正在运行,那么这些线程将共享单个地址空间。这些线程能够执行相同的代码,对相同的数据进行操作。这些线程还能共享内核对象句柄,因为句柄表依赖于每个进程而不是每个线程存在。
进程使用的系统资源比线程多得多。实际上,线程只有一个内核对象和一个堆栈,保留的记录很少,因此需要很少的内存。因此始终都应该设法用增加线程来解决编程问题,避免创建新的进程。但是许多程序设计用多个进程来实现会更好些。
2. 如何使用_beginthreadex函数?
使用方法与CreateThread函数相同,只是调用参数类型需要转换。
3. 如何使用CreateThread函数?
当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。使用时应当注意在不需要对线程内核进行访问后调用CloseHandle函数关闭线程句柄。因为CreateThread函数中使用某些C/C++运行期库函数时会有内存泄漏,所以应当尽量避免使用。
参数含义:
lpThreadAttributes 如果传递NULL该线程使用默认安全属性。如果希望所有的子进程能够继承该线程对象的句柄,必须将它的bInheritHandle成员被初始化为TRUE。
dwStackSize 设定线程堆栈的地址空间。如果非0,函数将所有的存储器保留并分配给线程的堆栈。如果是0,CreateThread就保留一个区域,并且将链接程序嵌入.exe文件的/STACK链接程序开关信息指明的存储器容量分配给线程堆栈。
lpStartAddress 线程函数的地址。
lpParameter 传递给线程函数的参数。
dwCreationFlags 如果是0,线程创建后立即进行调度。如果是CREATE_SUSPENDED,系统对它进行初始化后暂停该线程的运行。
LpThreadId 用来存放系统分配给新线程的ID。
4. 如何终止线程的运行?
(1) 线程函数返回(最好使用这种方法)。
这是确保所有线程资源被正确地清除的唯一办法。如果线程能够返回,就可以确保下列事项的实现:
在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。操作系统将正确地释放线程堆栈使用的内存。
系统将线程的退出代码设置为线程函数的返回值。系统将递减线程内核对象的使用计数。
(2) 调用ExitThread函数(最好不要使用这种方法)。
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。
(3) 调用TerminateThread函数(应该避免使用这种方法)。
TerminateThread能撤消任何线程。线程的内核对象的使用计数也被递减。TerminateThread函数是异步运行的函数。如果要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数。当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。
(4) 包含线程的进程终止运行(应该避免使用这种方法)。由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。就像从每个剩余的线程调用TerminateThread一样。这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。
5. 为什么不要使用_beginthread函数和_endthread函数?
与_beginthreadex函数相比参数少,限制多。无法创建暂停的线程,无法取得线程ID。_endthread函数无参数,线程退出代码必须为0。还有_endthread函数内部关闭了线程的句柄,一旦退出将不能正确访问线程句柄。
6. 如何对进程或线程的内核进行引用?
HANDLE GetCurrentProcess( );
HANDLE GetCurrentThread( );
这两个函数都能返回调用线程的进程的伪句柄或线程内核对象的伪句柄。伪句柄只能在当前的进程或线程中使用,在其它线程或进程将不能访问。函数并不在创建进程的句柄表中创建新句柄。调用这些函数对进程或线程内核对象的使用计数没有任何影响。如果调用CloseHandle,将伪句柄作为参数来传递,那么CloseHandle就会忽略该函数的调用并返回FALSE。
DWORD GetCurrentProcessId( );
DWORD GetCurrentThreadId( );
这两个函数使得线程能够查询它的进程的唯一ID或它自己的唯一ID。
7. 如何将伪句柄转换为实句柄?
HANDLE hProcessFalse = NULL;
HANDLE hProcessTrue = NULL;
HANDLE hThreadFalse = NULL;
HANDLE hThreadTrue = NULL;
hProcessFalse = GetCurrentProcess( );
hThreadFalse = GetCurrentThread( );
取得线程实句柄:
DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse, &hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
取得进程实句柄:
DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse, &hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
由于DuplicateHandle会递增特定对象的使用计数,因此当完成对复制对象句柄的使用时,应该将目标句柄传递给CloseHandle,从而递减对象的使用计数。
8. 在一个进程中可创建线程的最大数是得多少?
线程的最大数取决于该系统的可用虚拟内存的大小。默认每个线程最多可拥有至多1MB大小的栈的空间。所以,至多可创建2028个线程。如果减少默认堆栈的大小,则可以创建更多的线程。
线程的调度、优先级和亲缘性
9. 如何暂停和恢复线程的运行?
线程内核对象的内部有一个值指明线程的暂停计数。当调用CreateProcess或CreateThread函数时,就创建了线程的内核对象,并且它的暂停计数被初始化为1。因为线程的初始化需要时间,不能在系统做好充分的准备之前就开始执行线程。线程完全初始化好了之后,CreateProcess或CreateThread要查看是否已经传递了CREATE_SUSPENDED标志。如果已经传递了这个标志,那么这些函数就返回,同时新线程处于暂停状态。如果尚未传递该标志,那么该函数将线程的暂停计数递减为0。当线程的暂停计数是0的时候,除非线程正在等待其他某种事情的发生,否则该线程就处于可调度状态。在暂停状态中创建一个线程,就能够在线程有机会执行任何代码之前改变线程的运行环境(如优先级)。一旦改变了线程的环境,必须使线程成为可调度线程。方法如下:
hThread = CreatThread( ……,CREATE_SUSPENDED,…… );
或
bCreate = CreatProcess( ……,CREATE_SUSPENDED,……,pProcInfo );
if( bCreate != FALSE )
{
hThread = pProcInfo.hThread;
}
……
……
……
ResumeThread( hThread );
CloseHandle( hThread );
ResumeThread成功,它将返回线程的前一个暂停计数,否则返回0xFFFFFFFF。
单个线程可以暂停若干次。如果一个线程暂停了3次,它必须恢复3次。创建线程时,除了使用CREATE_SUSPENDED外,也可以调用SuspendThread函数来暂停线程的运行。任何线程都可以调用该函数来暂停另一个线程的运行(只要拥有线程的句柄)。线程可以自行暂停运行,但是不能自行恢复运行。与ResumeThread一样,SuspendThread返回的是线程的前一个暂停计数。线程暂停的最多次数可以是MAXIMUM_SUSPEND_COUNT次。SuspendThread与内核方式的执行是异步进行的,但是在线程恢复运行之前,不会发生用户方式的执行。调用SuspendThread时必须小心,因为不知道暂停线程运行时它在进行什么操作。只有确切知道目标线程是什么(或者目标线程正在做什么),并且采取强有力的措施来避免因暂停线程的运行而带来的问题或死锁状态,SuspendThread才是安全的。
10. 是否可以暂停和恢复进程的运行?
对于Windows来说,不存在暂停或恢复进程的概念,因为进程从来不会被安排获得CPU时间。不过Windows确实允许一个进程暂停另一个进程中的所有线程的运行,但是从事暂停操作的进程必须是个调试程序。特别是,进程必须调用WaitForDebugEvent和ContinueDebugEvent之类的函数。由于竞争的原因,Windows没有提供其他方法来暂停进程中所有线程的运行。
11. 如何使用sleep函数?
系统将在大约的指定毫秒数内使线程不可调度。Windows不是个实时操作系统。虽然线程可能在规定的时间被唤醒,但是它能否做到,取决于系统中还有什么操作正在进行。
可以调用Sleep,并且为dwMilliseconds参数传递INFINITE。这将告诉系统永远不要调度该线程。这不是一件值得去做的事情。最好是让线程退出,并还原它的堆栈和内核对象。可以将0传递给Sleep。这将告诉系统,调用线程将释放剩余的时间片,并迫使系统调度另一个线程。但是,系统可以对刚刚调用Sleep的线程重新调度。如果不存在多个拥有相同优先级的可调度线程,就会出现这种情况。
12. 如何转换到另一个线程?
系统提供了SwitchToThread函数。当调用这个函数的时候,系统要查看是否存在一个迫切需要CPU时间的线程。如果没有线程迫切需要CPU时间,SwitchToThread就会立即返回。如果存在一个迫切需要CPU时间的线程,SwitchToThread就对该线程进行调度(该线程的优先级可能低于调用SwitchToThread的线程)。这个迫切需要CPU时间的线程可以运行一个时间段,然后系统调度程序照常运行。该函数允许一个需要资源的线程强制另一个优先级较低、而目前却拥有该资源的线程放弃该资源。如果调用SwitchToThread函数时没有其他线程能够运行,那么该函数返回FALSE,否则返回一个非0值。调用SwitchToThread与调用Sleep是相似的。差别是SwitchToThread允许优先级较低的线程运行;而即使有低优先级线程迫切需要CPU时间,Sleep也能够立即对调用线程重新进行调度。
13. 如何取得线程运行的时间?
(1) 简单取得线程大概运行时间:
DWORD dwStartTime = 0;
DWORD dwEndTime = 0;
DWORD dwRunTime = 0;
dwStartTime = GetTickCount( );
……
……
……
dwEndTime = GetTickCount( );
dwRunTime = dwEndTime – dwStartTime;
(2) 调用GetThreadTimes的函数:
参数含义:
hThread 线程句柄
lpCreationTime 创建时间:英国格林威治时间
lpExitTime 退出时间:英国格林威治时间,如果线程仍然在运行,退出时间则未定义
lpKernelTime 内核时间:指明线程执行操作系统代码已经经过了多少个100ns的CPU时间
lpUserTime 用户时间:指明线程执行应用程序代码已经经过了多少个100ns的CPU时间
GetProcessTimes是个类似GetThreadTimes的函数,适用于进程中的所有线程(甚至是已经终止运行的线程)。返回的内核时间是所有进程的线程在内核代码中经过的全部时间的总和。GetThreadTimes和GetProcessTimes这两个函数在Windows98中不起作用。在Windows98中,没有一个可靠的机制可供应用程序来确定线程或进程已经使用了多少CPU时间。
14. 进程的优先级类有哪些?
优先级类 标识符 描述
实时 REALTIME_PRIORITY_CLASS 立即对事件作出响应,执行关键时间的任务。会抢先于操作系统组件之前运行。
高 HIGH_PRIORITY_CLASS 立即对事件作出响应,执行关键时间的任务。
高于正常 ABOVE_NORMAL_PRIORITY_CLASS 在正常优先级与高优先级之间运行(Windows2000)。
正常 NORMAL_PRIORITY_CLASS 没有特殊调度需求
低于正常 BELOW_NORMAL_PRIORITY_CLASS 在正常优先级与空闲优先级之间运行(Windows2000)。
空闲 IDLE_PRIORITY_CLASS 在系统空闲时运行。
设置方法:
BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriority );
DWORD GetPriorityClass( HANDLE hProcess );
使用命令外壳启动一个程序时,该程序的起始优先级是正常优先级。如果使用Start命令来启动该程序,可以使用一个开关来设定应用程序的起始优先级。例如:
c:/>START /LOW CALC.EXE
Start命令还能识别/BELOWNORMAL、/NORMAL、/ABOVENORMAL、/HIGH和/REALTIME等开关。
15. 线程的相对优先级有哪些?
相对优先级 标识符 描述
关键时间 THREAD_PRIORITY_TIME_CRITICAL 对于实时优先级类线程在优先级31上运行,对于其他优先级类,线程在优先级15上运行。
最高 THREAD_PRIORITY_HIGHEST 线程在高于正常优先级上两级上运行。
高于正常 THREAD_PRIORITY_ABOVE_NORMAL 线程在正常优先级上一级上运行。
正常 THREAD_PRIORITY_NORMAL 线程在进程的优先级类上正常运行。
低于正常 THREAD_PRIORITY_BELOW_NORMAL 线程在低于正常优先级下一级上运行。
最低 THREAD_PRIORITY_LOWEST 线程在低于正常优先级下两级上运行。
空闲 THREAD_PRIORITY_IDLE 对于实时优先级类线程在优先级16上运行对于其他优先级类线程在优先级1上运行。
设置方法:
BOOL SetThreadPriority( HANDLE hThread, DWORD dwPriority );
DWORD GetThreadPriorityClass( HANDLE hThread );
16. 如何避免系统动态提高线程的优先级等级?
系统常常要提高线程的优先级等级,以便对窗口消息或读取磁盘等I/O事件作出响应。或者当系统发现一个线程在大约3至4s内一直渴望得到CPU时间,它就将这个渴望得到CPU时间的线程的优先级动态提高到15,并让该线程运行两倍于它的时间量。当到了两倍时间量的时候,该线程的优先级立即返回到它的基本优先级。下面的函数可以对系统的调度方式进行设置:
BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL bDisableBoost );
BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pbDisableBoost );
BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL bDisableBoost );
BOOL GetThreadPriorityBoost( HANDLE hThread, PBOOL pbDisableBoost );
SetProcessPriorityBoost负责告诉系统激活或停用进行中的所有线程的优先级提高功能,而SetThreadPriorityBoost则激活或停用各个线程的优先级提高功能。Windows98没有提供这4个函数的有用的实现代码。
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=821374[/url]
使用类的成员函数作为线程的执行函数
我目前所做的项目是在Linux下的,项目中的某一部分需要在一个类中有“守护”线程,检查类中某些部分的状态并做出相应反映,下面介绍这部分的思路。1、 线程相关
首先在这里介绍一点关于线程的只是,但是不详细介绍,如果需要详细了解请参阅《POSIX线程详解》。
l pthread_create()函数
【声明】
#include <pthread.h>
int pthread_create(pthread_t *thread,
pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
【参数】
thread - 是pthread_t类型的指针。pthread_t类型在pthread.h中定义,通常称为“线程 id”(缩写为 "tid")。可以认为它是一种线程句柄;
attr - 用来定义线程的某些属性。一般只需将该参数设为 NULL,使用缺省属性即可;
start_routine - 新线程启动时调用的函数名。调用的函数把void * 作为参数,同时返回值的类型也是 void *,这表明可以用 void * 向新线程传递任意类型的数据,新线程完成时也可返回任意类型的数据;
arg - 向线程所调用的函数中传递参数的参数。
【返回值】
=0 - 成功创建线程;
≠0 - 创建失败。如:
EAGAIN - 在errno.h中定义,“#define EAGAIN 11”系统没有足够的资源来新建线程(有可能是你的线程数量已经超出最大线程数量,这个数量在由PTHREAD_THREADS_MAX这个宏定义指出);
ENOMEM - 在error.h中定义,“#define ENOMEM 12”内存不足。
l pthread_cancel()
【声明】
#include <pthread.h>
int pthread_cancel(pthread_t thread);
【参数】
thread - 待取消的目标线程,函数向thread所指向的线程发送cancel请求。
【返回值】
=0 - 执行成功;
≠0 - 执行失败。如:
ESRCH - 没有找到pthread所指向的线程。
2、 我的问题
上面介绍了线程的相关知识,下面按照我当时的思考过程逐步介绍,希望能够对大家有所启发。
我所做的项目需要完成这样的功能,每个实例有自己的独立的空间,“守护线程”处理类内部的事物。它们之间又有数据交互,接到数据后进行相应的处理再发送出去。类有接收数据的缓冲区,防止处理不及时,“守护线程”发现缓冲区里面有数据则处理,没有则空转。
首先分析问题,我们需要建立一个类,类中有线程变量、线程所要执行的函数以及其它一些相关的信息,类声明如下:
#include <pthread.h>
#include <error.h>
class Device
{
private:
pthread_t m_thread;
void *tfun(void *arg);
void Destroy(void);
public:
Device(args);
~Device();
int Init(args);
......
};
【说明】
(1)声明中的"args"代表参数若干,下同。
(2)这个工程没有采用异常机制,根据返回值来确定函数执行情况。构造函数没有返回值,不能反映运行的状态,而建立线程有可能产生错误,所以引入Init()函数。把不会产生错误的操作(如赋初值等)放在构造函数里面,把所有可能产生错误的操作都放在这个函数里面,这样就可以得知程序的初始化结果。
到了这一步,我理所当然的写下了如下的实现语句:
int Device::Init(args)
{
int errNo = 0;
errNo = pthread(&m_thread, NULL, tfun, NULL);
if ( errNo )
{
goto END;
}
......
END:
Destroy();
return errNo;
}
【说明】
(1)现在基本不提倡使用goto语句,但是我觉得可以单向使用goto语句,这样即不会跳来跳去的使人迷惑,又可以提高效率,亦可以使函数能够具有“单入口单出口”(只有一个return语句,而不是每个错误处直接用return返回错误值);
(2)也许高手一眼就看出来这里有问题了,但是我当时没有看出来,编译后提示:
main.cpp: In member function `int Device::Init(int)':
main.cpp:XX: no matches converting function `tfun' to type `void*(*)(void*)'
main.cpp:XX: candidates are: void* Device::tfun(void*)
当时想了许久,参数改了又改,还是编译不过,后来才知道,非静态成员函数不能作为线程的执行函数!看来得改思路了,想了一下有两种方法可行:第一、把成员函数声明为静态函数;第二、另外写一个非成员函数void *Fun(viod *arg)作为线程的执行函数。但是这两中方法都有自己的问题。第一种方法,静态函数不能访问非静态变量,虽然他是类的成员函数,但是访问不到所有变量。第二种方法,需要将实例的指针传给函数,但是函数又只有访问public变量的权限。
对于第一种方案,我们需要进行如下调整:(1)函数声明为静态函数;(2)在调用函数tFun()的时候将本实例的指针this以void *的形式传递进去;(3)在函数tFun()的内部增加指针转化语句“Device *mthis = (Device *)arg;”。
对于第二种方案,我们需要进行如下调整:(1)将函数声明为类Device的友元函数(否则访问不到非public成员变量);(2)在调用函数Fun()的时候将本实例的指针this以void *的形式传递进去;(3)在函数Fun()的内部增加指针转化语句“Device *mthis = (Device *)arg;”。
想比较而言,我认为第一种方案结构比较紧凑,使用起来也比较方便,遂整理代码如下:
#include <pthread.h>
#include <error.h>
class Device
{
private:
void Destroy(void);
pthread_t m_thread;
static void *tfun(void *arg);
public:
Device(void);
~Device();
int Init(int args);
};
int Device::Init(int args)
{
int errNo = 0;
errNo = pthread_create(&m_thread, NULL, tfun, this);
if ( errNo )
{
goto END;
}
......
END:
return errNo;
}
void *Device::tfun(void *arg)
{
Device *mthis = (Device *)arg;
......
}
至此,问题得到解决。
上一篇:编程经验点滴(三)——《C、C++中指针加 1 的问题(2004-11-23)》
下一篇:
声明:原创,版权所有,如需转载请注明出处。
[url]http://blog.csdn.net/blankman/archive/2005/03/29/333031.aspx[/url]
PS:现在csdn的编辑功能越来越难用了 -_-!! 调不动了,以后做个模板再说吧
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=333031[/url]
看孙鑫老师VC++视频教程笔记 之 多线程编程(二)
一、线程的同步创建互斥对象完成线程同步 :
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName );
打开一个命名的或者没有名字的互斥对象:
参数1:指向SECURITY_ATTRIBUTES结构体的指针。可以传递NULL,让其使用默认的安全性。
参数2:指示互斥对象的初始拥有者。如果该值是真,调用者创建互斥对象,调用的线程获得互斥对象
的所有权。否则,调用线程捕获的互斥对象的所有权。(就是说,如果该参数为真,则调用
该函数的线程拥有互斥对象的所有权。否则,不拥有所有权)
参数3:互斥对象名称。传递NULL创建的就是没有名字的互斥对象,即匿名的互斥对象。
返回值:如果函数成功,返回Mutex对象的一个句柄。如果命名的互斥对象在调用函数前已经存在,
函数返回已经存在的互斥对象的句柄,然后调用GetLastError返回ERROR_ALREADY_EXISTS。
否则,调用者创建互斥对象。
例://创建匿名互斥对象
//当前没有线程拥有互斥对象,操作系统会将互斥对象设置为已通知状态(有信号状态)
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
另:关于互斥对象
*互斥对象(Mutex)属于内核对象,它能确保线程拥有对单个资源的互斥访问权。
*互斥对象包含一个使用数量,一个线程ID(哪个线程拥有互斥对象,就设置为哪个线程的线程ID)和一个计数器。
*ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
二、在线程中请求互斥对象
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
出现下面情况之一时返回:
1、指定的对象处于有信号状态;
2、超出了超时(time-out)的时间间隔。
参数1:对象的句柄,这里传递的是互斥对象的句柄。一旦互斥对象变成有信号状态,该函数返回。
如果互斥对象始终没有处于有信号状态(非信号状态),函数将一直处于等待,导致线程
暂停运行。
参数2:指定超时的时间间隔,以毫秒为单位。
如果时间间隔流逝了,函数就返回,即使等待的互斥对象处于非信号状态;
如果将该参数设置为0,该函数测试对象的状态然后立即返回;
如果将该参数设置为INFINITE,函数的超时值永远不会发生,也就是说函数将永远等待,直到
所等待的对象处于有信号状态。
注意:可以在我们需要保护的代码前面加上WaitForSingleObject(),当我们请求互斥对象的时候
操作系统会判断请求互斥对象的线程和拥有互斥对象的线程的ID是否相等,如果相等,即使
互斥对象处于未通知状态(非信号状态),仍然能够获得互斥对象的所有权。操作系统通过
互斥对象的计数器记录请求了多少次互斥对象。
另:可通过该函数的返回值,得知如何获得的互斥对象所有权。
例://除非等待的互斥对象变为有信号状态,才继续运行,否则永远等待
WaitForSingleObject(hMutex, INFINITE);
//如果互斥对象处于有信号状态(已通知状态)则得到互斥对象,并将互斥对象设置为
//未通知状态(非信号状态),然后继续运行。
三、释放互斥对象
释放互斥对象,使互斥对象处于已通知状态(有信号状态):
BOOL ReleaseMutex( HANDLE hMutex ); 释放指定互斥对象的所有权。如果成功返回非0值,失败返回0。 在需要保护的代码后调用ReleaseMutex释放互斥对象,操作系统会将互斥对象的线程ID设置为0, 并将互斥对象设置为已通知状态(有信号状态)。同时,互斥对象的计数器减一。 注意:调用该函数的时候,操作系统会判断调用线程ID与互斥对象内部所维护的线程ID号是否相等,如果 不等,互斥对象就不能被释放。 释放互斥对象的原则:谁拥有,谁释放。 四、调用的形式 //在主线程中 ... HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); ... //其他线程中 ... WaitForSingleObject(hMutex, INFINITE); //受保护的代码 ... ReleaseMutex(hMutex);
五、互斥对象的一个应用
//避免同一程序运行多次
hMutex = CreateMutex(NULL, FALSE, "mutex");
if (hMutex)
{
if ( ERROR_ALREADY_EXISTS == GetLastError() )
{
cout<<"仅可以运行一个实例!"<<endl;
return;
}
}
Trackback: [url]http://tb.blog.csdn.net/TrackBack.aspx?PostId=424219[/url] 看的头痛,顶 学习…………………………
页:
[1]