c++的多线程编程
来源:互联网 发布:编程游戏要钱吗 编辑:程序博客网 时间:2024/06/03 13:12
一 简单实例
比较简单的代码,创建10个线程,其中使第4个线程在一创建就挂起,等到其他的线程执行的差不多的时候再使第4个线程恢复执行。
#include <stdlib.h>
#include <windows.h>
#define THREAD_NUM 10
DWORD WINAPI PrintThreads (LPVOID);
int main ()
{
HANDLE hThread[THREAD_NUM];
DWORD dwThreadID[THREAD_NUM];
for (int i=0; i<THREAD_NUM; ++i)
{
int isStartImmediate = 0;
if(3 == i)
isStartImmediate = CREATE_SUSPENDED;
hThread[i]=CreateThread(NULL, // security attributes that should be applied to the new thread,
// this is for NT. Use NULL to get the default security attributes. Use NULL for win95
0, // default size of 1MB can be passed by passing zero.
PrintThreads, // function name:address of the function where the new thread starts.
(LPVOID)i, // parameter(void pointer): pointer to the 32 bit parameter that will be passed into the thread
isStartImmediate, // flags to control the creation of the thread. Passing zero starts the thread immediately.
// Passing CREATE_SUSPENDED suspends the thread until the ResumeThread( ) function is called.
&dwThreadID[i] // pointer to a 32-bit variable that receives the thread identifier.
);
if (hThread[i])
{
printf ("Thread launched successfully\n");
}
}
printf("Start sleep 100, and let other thread excute\n");
Sleep (100);
printf("Start sleep 100, and thread 3 excute\n");
ResumeThread(hThread[3]);
Sleep(100);
for(int i = 0; i<THREAD_NUM; ++i)
{
if (hThread[i])
{
CloseHandle(hThread[i]); // You need to use this to release kernel objects when you are done using them.
// If a process exits without closing the thread handle,
// the operating system drops the reference counts for those objects.
// But if a process frequently creates threads without closing the handles,
// there could be hundreds of thread kernel objects lying around and these resource leaks can have a big hit on performance.
}
}
return (0);
}
//function PrintThreads
DWORD WINAPI PrintThreads (LPVOID num)
{
for (int i=0; i<10; i++)
printf ("Thread Number is %d%d%d\n", num,num,num);
return 0;
}
二 其他基本API的说明
CreateThread() 调用成功返回句柄和一个id。
CloseHandle() 关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄.
SuspendThread(HANDLE) 允许开发人员将HANDLE指定的线程挂起,如果要挂起的线程占有共享资源,则可能导致死锁。
ResumeThread(HANDLE) 恢复指定的线程。
TerminateThread() 立即终止线程的工作,不做任何清理工作。
ExitThread() 线程函数返回时回调用次函数,所以一般我们不去显示的调用。
ExitThread是推荐使用的结束一个线程的方法,当调用该函数时,当前线程的栈被释放,然后线程终止,相对于TerminateThread函数来说,这样做能够更好地完成附加在该线程上的DLL的清除工作. 但是ExitThread()会导致线程在清处构造器/自动变量之前就终止,所以我们最好不要显示的调用ExitThread()。
C/C++ Runtime 多线程函数
一 简单实例(来自codeprojct:http://www.codeproject.com/useritems/MultithreadingTutorial.asp)
主线程创建2个线程t1和t2,创建时2个线程就被挂起,后来调用ResumeThread恢复2个线程,是其开始执行,调用WaitForSingleObject等待2个线程执行完,然后推出主线程即结束进程。
*
* This program is an adaptation of the code Rex Jaeschke showed in
* Listing 1 of his Oct 2005 C/C++ User's Journal article entitled
* "C++/CLI Threading: Part I". I changed it from C++/CLI (managed)
* code to standard C++.
*
* One hassle is the fact that C++ must employ a free (C) function
* or a static class member function as the thread entry function.
*
* This program must be compiled with a multi-threaded C run-time
* (/MT for LIBCMT.LIB in a release build or /MTd for LIBCMTD.LIB
* in a debug build).
*
* John Kopplin 7/2006
*/
#include <stdio.h>
#include <string> // for STL string class
#include <windows.h> // for HANDLE
#include <process.h> // for _beginthread()
using namespace std;
class ThreadX
{
private:
int loopStart;
int loopEnd;
int dispFrequency;
public:
string threadName;
ThreadX( int startValue, int endValue, int frequency )
{
loopStart = startValue;
loopEnd = endValue;
dispFrequency = frequency;
}
// In C++ you must employ a free (C) function or a static
// class member function as the thread entry-point-function.
// Furthermore, _beginthreadex() demands that the thread
// entry function signature take a single (void*) and returned
// an unsigned.
static unsigned __stdcall ThreadStaticEntryPoint(void * pThis)
{
ThreadX * pthX = (ThreadX*)pThis; // the tricky cast
pthX->ThreadEntryPoint(); // now call the true entry-point-function
// A thread terminates automatically if it completes execution,
// or it can terminate itself with a call to _endthread().
return 1; // the thread exit code
}
void ThreadEntryPoint()
{
// This is the desired entry-point-function but to get
// here we have to use a 2 step procedure involving
// the ThreadStaticEntryPoint() function.
for (int i = loopStart; i <= loopEnd; ++i)
{
if (i % dispFrequency == 0)
{
printf( "%s: i = %d\n", threadName.c_str(), i );
}
}
printf( "%s thread terminating\n", threadName.c_str() );
}
};
int main()
{
// All processes get a primary thread automatically. This primary
// thread can generate additional threads. In this program the
// primary thread creates 2 additional threads and all 3 threads
// then run simultaneously without any synchronization. No data
// is shared between the threads.
// We instantiate an object of the ThreadX class. Next we will
// create a thread and specify that the thread is to begin executing
// the function ThreadEntryPoint() on object o1. Once started,
// this thread will execute until that function terminates or
// until the overall process terminates.
ThreadX * o1 = new ThreadX( 0, 1, 2000 );
// When developing a multithreaded WIN32-based application with
// Visual C++, you need to use the CRT thread functions to create
// any threads that call CRT functions. Hence to create and terminate
// threads, use _beginthreadex() and _endthreadex() instead of
// the Win32 APIs CreateThread() and EndThread().
// The multithread library LIBCMT.LIB includes the _beginthread()
// and _endthread() functions. The _beginthread() function performs
// initialization without which many C run-time functions will fail.
// You must use _beginthread() instead of CreateThread() in C programs
// built with LIBCMT.LIB if you intend to call C run-time functions.
// Unlike the thread handle returned by _beginthread(), the thread handle
// returned by _beginthreadex() can be used with the synchronization APIs.
HANDLE hth1;
unsigned uiThread1ID;
hth1 = (HANDLE)_beginthreadex( NULL, // security
0, // stack size
ThreadX::ThreadStaticEntryPoint,
o1, // arg list
CREATE_SUSPENDED, // so we can later call ResumeThread()
&uiThread1ID );
if ( hth1 == 0 )
printf("Failed to create thread 1\n");
DWORD dwExitCode;
GetExitCodeThread( hth1, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259
printf( "initial thread 1 exit code = %u\n", dwExitCode );
// The System::Threading::Thread object in C++/CLI has a "Name" property.
// To create the equivalent functionality in C++ I added a public data member
// named threadName.
o1->threadName = "t1";
ThreadX * o2 = new ThreadX( -1000000, 0, 2000 );
HANDLE hth2;
unsigned uiThread2ID;
hth2 = (HANDLE)_beginthreadex( NULL, // security
0, // stack size
ThreadX::ThreadStaticEntryPoint,
o2, // arg list
CREATE_SUSPENDED, // so we can later call ResumeThread()
&uiThread2ID );
if ( hth2 == 0 )
printf("Failed to create thread 2\n");
GetExitCodeThread( hth2, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259
printf( "initial thread 2 exit code = %u\n", dwExitCode );
o2->threadName = "t2";
// If we hadn't specified CREATE_SUSPENDED in the call to _beginthreadex()
// we wouldn't now need to call ResumeThread().
ResumeThread( hth1 ); // serves the purpose of Jaeschke's t1->Start()
ResumeThread( hth2 );
// In C++/CLI the process continues until the last thread exits.
// That is, the thread's have independent lifetimes. Hence
// Jaeschke's original code was designed to show that the primary
// thread could exit and not influence the other threads.
// However in C++ the process terminates when the primary thread exits
// and when the process terminates all its threads are then terminated.
// Hence if you comment out the following waits, the non-primary
// threads will never get a chance to run.
WaitForSingleObject( hth1, INFINITE );
WaitForSingleObject( hth2, INFINITE );
GetExitCodeThread( hth1, &dwExitCode );
printf( "thread 1 exited with code %u\n", dwExitCode );
GetExitCodeThread( hth2, &dwExitCode );
printf( "thread 2 exited with code %u\n", dwExitCode );
// The handle returned by _beginthreadex() has to be closed
// by the caller of _beginthreadex().
CloseHandle( hth1 );
CloseHandle( hth2 );
delete o1;
o1 = NULL;
delete o2;
o2 = NULL;
printf("Primary thread terminating.\n");
}
二解释
1)如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用VisualC++运行期库函数_beginthreadex,推出也应该使用_endthreadex。如果不使用Microsoft的VisualC++编译器,你的编译器供应商有它自己的CreateThred替代函数。不管这个替代函数是什么,你都必须使用。
2)因为_beginthreadex和_endthreadex是CRT线程函数,所以必须注意编译选项runtimelibaray的选择,使用MT或MTD。
3) _beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同。这是因为Microsoft的C/C++运行期库的开发小组认为,C/C++运行期函数不应该对Windows数据类型有任何依赖。_beginthreadex函数也像CreateThread那样,返回新创建的线程的句柄。
下面是关于_beginthreadex的一些要点:
•每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的VisualC++源代码中)。
•传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。
•_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
•当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
•如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。
4) _endthreadex的一些要点:
•C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。
•然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。
5)虽然也提供了简化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。
6)线程handle因为是内核对象,所以需要在最后closehandle。
7)更多的API:HANDLE GetCurrentProcess();HANDLE GetCurrentThread();DWORD GetCurrentProcessId();DWORD GetCurrentThreadId()。DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);BOOL SetThreadPriority(HANDLE hThread,int nPriority);BOOL SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();
三注意
1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。所以上面的代码中如果不调用WaitForSingleObject,则2个子线程t1和t2可能并没有执行完毕或根本没有执行。
2)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。所以上面的代码如果不调用resumethread,则会死锁。
多线程同步之Critical Sections(功能与Mutex相同,保证某一时刻只有一个线程能够访问共享资源,但是不是内核对象,所以访问速度要比Mutex快,但是增没有等待超时的功能,所以有可能会导致死锁,使用时可以根据实际的情况选择其一)
一 Critical Sections
1) 因为Critical Sections不是内核对象,所以只能用来统一进程内线程间的同步,不能用来多个不同进程间的线程的同步。
2) 如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则结果是改线程所对应的内核不能被释放,该线程成为死线程。
3) 要比其他的内核对象的速度要快。
二 使用CriticalSections的简单实例,Stack在push的时候可以分为3个步骤,看下面的代码,但是如果在第2步后此线程中断切换到其他的线程,其他的线程push后再返回执行时,此线程继续执行,这样有可能刚才其他线程push就会被覆盖了,在stack里找不到了。(下面的代码在debug下使用了CriticalSection,release下可能有问题)
#include <process.h>
#include <stdio.h>
/////////////////////////////////////////////
//stack:
struct Node
{
struct Node *next;
int data;
};
struct Stack
{
struct Node *head;
#ifdef _DEBUG
CRITICAL_SECTION critical_sec;
#endif
Stack()
{
head = NULL;
#ifdef _DEBUG
InitializeCriticalSection(&critical_sec);
#endif
}
~Stack()
{
if(head != NULL)
{
if(NULL == head->next)
{
delete head;
head = NULL;
}
else
{
Node *p = head;
Node *q = head->next;
while(q != NULL)
{
delete p;
p = q;
q = q->next;
};
delete p;
p = NULL;
}
}
#ifdef _DEBUG
DeleteCriticalSection(&critical_sec);
#endif
}
void Push (int num)
{
//enter critical section, add a new node and then
#ifdef _DEBUG
EnterCriticalSection (&critical_sec);
#endif
Node * node = new Node();
node->next = head;
node->data = num;
head = node;
printf("Stack:%d\n",num);
//leave critical section
#ifdef _DEBUG
LeaveCriticalSection (&critical_sec);
#endif
}
int Pop ()
{
#ifdef _DEBUG
EnterCriticalSection (&critical_sec);
#endif
int result = 0;
if(head!= NULL)
{
result = head->data;
if(head->next != NULL)
{
Node *temp = head->next;
delete head;
head = temp;
}
else
head = NULL;
}
#ifdef _DEBUG
LeaveCriticalSection (&critical_sec);
#endif
return result;
}
};
//////////////////////////////////////////////////////
//test:
unsigned __stdcall Thread1(void * pVoid)
{
Stack *stack = ((Stack*)pVoid);
for(int i = 200; i<220;++i)
{
stack->Push(i);
}
return 1;
}
unsigned __stdcall Thread2(void *pVoid)
{
Stack *stack = ((Stack*)pVoid);
for(int i = 0; i<20; ++i)
{
stack->Push(i);
}
return 1;
}
int main()
{
Stack stack;
stack.Push(1000);
stack.Push(1000);
HANDLE hth1;
unsigned uiThread1ID;
hth1 = (HANDLE)_beginthreadex( NULL, // security
0, // stack size
Thread1,
(void*)&stack, // arg list
CREATE_SUSPENDED, // so we can later call ResumeThread()
&uiThread1ID );
if ( hth1 == 0 )
printf("Failed to create thread 1\n");
DWORD dwExitCode;
GetExitCodeThread( hth1, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259
printf( "initial thread 1 exit code = %u\n", dwExitCode );
HANDLE hth2;
unsigned uiThread2ID;
hth2 = (HANDLE)_beginthreadex( NULL, // security
0, // stack size
Thread2,
(void*)&stack, // arg list
CREATE_SUSPENDED, // so we can later call ResumeThread()
&uiThread2ID );
if ( hth2 == 0 )
printf("Failed to create thread 2\n");
GetExitCodeThread( hth2, &dwExitCode ); // should be STILL_ACTIVE = 0x00000103 = 259
printf( "initial thread 2 exit code = %u\n", dwExitCode );
ResumeThread( hth1 );
ResumeThread( hth2 );
WaitForSingleObject( hth1, INFINITE );
WaitForSingleObject( hth2, INFINITE );
GetExitCodeThread( hth1, &dwExitCode );
printf( "thread 1 exited with code %u\n", dwExitCode );
GetExitCodeThread( hth2, &dwExitCode );
printf( "thread 2 exited with code %u\n", dwExitCode );
CloseHandle( hth1 );
CloseHandle( hth2 );
printf("Primary thread terminating.\n");
}
三 对Critical Section的封装:
//////////////////////////////////////////////////////
// 方法一: Lock中的CritSect成员变量必须是引用类型。
{
public:
friend class Lock;
CritSect() { InitializeCriticalSection(&_critSection); }
~CritSect() { DeleteCriticalSection(&_critSection); }
private:
void Acquire(){EnterCriticalSection(&_critSection);}
void Release(){LeaveCriticalSection(&_critSection);}
CRITICAL_SECTION _critSection;
};
class Lock
{
public:
Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }
~Lock(){_critSect.Release();}
private:
CritSect& _critSect;
};
//////////////////////////////////////////////////////
//方法二:
class CLock {
public:
CLock() { InitializeCriticalSection (&m_criticalSection); }
void Lock () { EnterCriticalSection (&m_criticalSection); }
void Unlock () { LeaveCriticalSection (&m_criticalSection); }
virtual ~CLock() { DeleteCriticalSection (&m_criticalSection); }
private:
CRITICAL_SECTION m_criticalSection;
};
// Scoped MT-exclusive lock
class CScopedLocker {
public:
CScopedLocker (CLock * t) : m_lock (t) { m_lock->Lock(); }
~CScopedLocker() { m_lock->Unlock(); }
private:
CLock * m_lock;
};
对上面的2中封装的调用都比较简单,都是只有2行代码。
CritSect sect;
Lock lock(sect);
或
CLock t;
CSCopedLocker st(&t);
下面的对封装的测试代码,保证了对g_n全局变量在线程1操作结束后线程2才可以操作。(下面的代码因为对全局变量同步,所以需要申明含有CRITICAL_SECTION的类为全局)
#include<iostream>
using namespace std;
//////////////////////////////////////////////////////
// ·½·¨Ò»£º
class CritSect
{
public:
friend class Lock;
CritSect() { InitializeCriticalSection(&_critSection); }
~CritSect() { DeleteCriticalSection(&_critSection); }
private:
void Acquire(){EnterCriticalSection(&_critSection);}
void Release(){LeaveCriticalSection(&_critSection);}
CRITICAL_SECTION _critSection;
};
class Lock
{
public:
Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }
~Lock(){_critSect.Release();}
private:
CritSect& _critSect;
};
//////////////////////////////////////////////////////
//·½·¨¶þ£º
// MT-exclusive lock
class CLock {
public:
CLock() { InitializeCriticalSection (&m_criticalSection); }
void Lock () { EnterCriticalSection (&m_criticalSection); }
void Unlock () { LeaveCriticalSection (&m_criticalSection); }
virtual ~CLock() { DeleteCriticalSection (&m_criticalSection); }
private:
CRITICAL_SECTION m_criticalSection;
};
// Scoped MT-exclusive lock
class CScopedLocker {
public:
CScopedLocker (CLock * t) : m_lock (t) { m_lock->Lock(); }
~CScopedLocker() { m_lock->Unlock(); }
private:
CLock * m_lock;
};
// ¶ÔÈ«¾ÖµÄ±äÁ¿£¬Ê¹ÓÃCritical Section
// Declare the global variable
static int g_n;
CritSect sect;
//CLock t;
////////Thread One Function///////////////////
UINT ThreadOne(LPVOID lParam)
{
Lock lock(sect);
//CScopedLocker st(&t);
for(int i=0;i<100;i++)
{
g_n++;
cout << "Thread 1: " << g_n << "\n";
}
// return the thread
return 0;
}
////////Thread Two Function///////////////////
UINT ThreadTwo(LPVOID lParam)
{
Lock lock(sect);
//CScopedLocker st(&t);
for(int i=300;i<400;i++)
{
g_n++;
cout << "Thread 2: "<< g_n << "\n";
}
// return the thread
return 0;
}
int main()
{
// Create the array of Handle
HANDLE hThrd[2];
//Thread ID's
DWORD IDThread1, IDThread2;
// Create thredas use CreateThread function with NULL Security
hThrd[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) ThreadOne,(LPVOID)NULL,0,&IDThread1);
hThrd[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) ThreadTwo,(LPVOID)NULL,0,&IDThread2);
// Wait for the main thread
WaitForMultipleObjects(2,hThrd,TRUE,INFINITE);
return 0;
}
四 API列表:
多线程同步之WIN API互锁函数 (可以避免使用CriticalSection或Mutex)
一 互锁函数
互锁函数的家族十分的庞大,可以查看msdn(http://msdn2.microsoft.com/en-us/library/ms686360.aspx)以InterLocked开始的函数都是户数函数。使用互锁函数的优点是:他的速度要比其他的CriticalSection,Mutex,Event,Semaphore快很多。
二 简单实例
使用一些实例说明部分互锁函数的使用:
1) LONG InterlockedExchangeAdd( PLONG plAddend,LONG Increment);
简单实例,在线程函数中对全局的变量自增,在开始使其为0,在线程都执行完以后输出全局变量的值,如果我们不使用互锁函数,则最后输出的结果,大部分情况是不正确的,比如我们一共有10000个线程的话,则全局变量的值一般是比10000要小;但是如果我们使用互锁函数来实现自增,则就快速的实现了线程安全,最后输出的全局变量一定是10000.
#include <process.h>
#include <stdio.h>
#define THREAD_MAX 100000
int g_x = 0;
unsigned __stdcall ThreadEntity(void * pVoid)
{
g_x++;
//InterlockedExchangeAdd(reinterpret_cast<long*>(&g_x),1);
return 1;
}
int main()
{
HANDLE hth[THREAD_MAX];
unsigned uiThreadID[THREAD_MAX];
printf("start create children threadings:\n");
for(int i = 0; i < THREAD_MAX; ++i)
{
hth[i] = (HANDLE)_beginthreadex( NULL, // security
0, // stack size
ThreadEntity,
(void*)&i, // arg list
0,
&uiThreadID[i] );
if ( hth[i]== 0 )
printf("Failed to create thread 1\n");
}
WaitForMultipleObjects( THREAD_MAX, hth,true,10000);
for(int i = 0; i<THREAD_MAX; ++i)
CloseHandle( hth[i] );
printf("last: g_x is %d\n",g_x);
printf("Primary thread terminating.\n");
}
在上面的代码中使用了 //InterlockedExchangeAdd(reinterpret_cast<long*>(&g_x),1); 来实现g_x的线程安全的自增。
2)使用LONG InterlockedExchange(PLONG plTarget,LONG lValue);实现循环锁:
BOOL g_fResourceInUse = FALSE;
void Func1()
{
//Wait to access the resource.
while(InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)
Sleep(0);
//Access the resource.
//We no longer need to access the resource.
InterlockedExchange(&g_fResourceInUse, FALSE);
}
3)太多了,不举了,以后用到了再放到这里把。
三 互锁函数列表
一般的互锁函数:
链表的互锁函数:
多线程之等待函数
一 等待函数
1)函数列举
2)简单说明WaitForSingleObject
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
参数hObject:要等待的内核对象的句柄。
参数dwMilliseconds: 设置的等待超时的时间,以毫秒为单位。可以设置为INGINIT。
顺便说一下,INFINITE已经定义为0xFFFFFFFF(或-1)。当然,传递INFINITE有些危险。如果对象永远不变为已
通知状态,那么调用线程永远不会被唤醒,它将永远处于死锁状态。
返回值:WAIT_OBJECT_0表示要等待的对象已经变为已通知的状态。
WAIT_TIMEOUT表示设置的时间超时。
WAIT_FAILED表示失败,可能是传入的handle不正确或其他的问题。
switch(dw)
{
case WAIT_OBJECT_0:
// The process terminated.
break;
case WAIT_TIMEOUT:
// The process did not terminate within 5000 milliseconds.
break;
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
}
3)简单说明WaitForMultipleObjects
DWORD WaitForMultipleObjects(DWORD dwCount,CONST HANDLE* phObjects,BOOL fWaitAll,DWORD dwMilliseconds);
参数dwCout:需要等待的内核对象的数量。
参数phObjects:需要等待的内核对象的是数组的指针。
参数fWaitAll:表示是否需要等待所有的内核对象。
参数dwMilliseconds:设置等待超时的时间。(同上函数)
返回值:WAIT_FAILED和WAIT_TIMEOUT同上函数。
如果为fWaitAll参数传递TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE ,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0+dwCount- 1)之间的一个值。
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch(dw)
{
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
case WAIT_TIMEOUT:
// None of the objects became signaled within 5000 milliseconds.
break;
case WAIT_OBJECT_0 + 0:
// The process identified by h[0] (hProcess1) terminated.
break;
case WAIT_OBJECT_0 + 1:
// The process identified by h[1] (hProcess2) terminated.
break;
case WAIT_OBJECT_0 + 2:
// The process identified by h[2] (hProcess3) terminated.
break;
}
多线程之线程同步Mutex (功能与CriticalSection相同,保证某一时刻只有一个线程能够访问共享资源,但是是内核对象,所以访问速度要比CriticalSection要慢,但是增加了等待超时的功能,使用时可以根据实际的情况选择其一)
一 Mutex
互斥对象(mutex)内核对象能够确保线程拥有对单个资源的互斥访问权。实际上互斥对象是因此而得名的。互斥对象包含一个使用数量,一个线程ID和一个递归计数器。
互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
ID用于标识系统中的哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对象的次数。
互斥对象有许多用途,属于最常用的内核对象之一。通常来说,它们用于保护由多个线程访问的内存块。如果多个线程要同时访问内存块,内存块中的数据就可能遭到破坏。互斥对象能够保证访问内存块的任何线程拥有对该内存块的独占访问权,这样就能够保证数据的完整性。
互斥对象的使用规则如下:
• 如果线程ID是0(这是个无效ID),互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。
• 如果ID是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。
• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则。
若要使用互斥对象,必须有一个进程首先调用CreateMutex,以便创建互斥对象:
HANDLECreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);
InitialOwner参数用于控制互斥对象的初始状态。如果传递FALSE(这是通常情况下传递的值),那么互斥对象的ID和递归计数器均被设置为0。这意味着该互斥对象没有被任何线程所拥有,因此要发出它的通知信号。
如果为fInitialOwner参数传递TRUE,那么该对象的线程ID被设置为调用线程的ID,递归计数器被设置为1。由于ID是个非0数字,因此该互斥对象开始时不发出通知信号。
通过调用一个等待函数,并传递负责保护资源的互斥对象的句柄,线程就能够获得对共享资源的访问权。在内部,等待函数要检查线程的ID,以了解它是否是0(互斥对象发出通知信号)。如果线程ID是0,那么该线程ID被设置为调用线程的ID,递归计数器被设置为1,同时,调用线程保持可调度状态。
如果等待函数发现ID不是0(不发出互斥对象的通知信号),那么调用线程便进入等待状态。系统将记住这个情况,并且在互斥对象的ID重新设置为0时,将线程ID设置为等待线程的ID,将递归计数器设置为1,并且允许等待线程再次成为可调度线程。与所有情况一样,对互斥内核对象进行的检查和修改都是以原子操作方式进行的。
一旦线程成功地等待到一个互斥对象,该线程就知道它已经拥有对受保护资源的独占访问权。试图访问该资源的任何其他线程(通过等待相同的互斥对象)均被置于等待状态中。当目前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用ReleaseMutex函数来释放该互斥对象:
BOOL ReleaseMutex(HANDLE hMutex);
该函数将对象的递归计数器递减1。
当该对象变为已通知状态时,系统要查看是否有任何线程正在等待互斥对象。如果有,系统将“按公平原则”选定等待线程中的一个,为它赋予互斥对象的所有权。当然,这意味着线程I D被设置为选定的线程的ID,并且递归计数器被置为1。如果没有其他线程正在等待互斥对象,那么该互斥对象将保持已通知状态,这样,等待互斥对象的下一个线程就立即可以得到互斥对象。
二 API
三 实例
来自msdn的实例:在线程函数中有一个循环,在每个循环的开始都取得Mutex,然后对全局或静态操作,相当于在关键代码段操作,然后在使用完以后释放它,大家可以执行,查看结果。
#include <stdio.h>
#define THREADCOUNT 64 //less than 64
HANDLE ghMutex;
int g_x = 0;
DWORD WINAPI WriteToDatabase(LPVOID);
void main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
int i;
// Create a mutex with no initial owner
ghMutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (ghMutex == NULL)
{
printf("CreateMutex error: %d\n", GetLastError());
return;
}
// Create worker threads
for( i=0; i < THREADCOUNT; i++ )
{
aThread[i] = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE) WriteToDatabase,
NULL, // no thread function arguments
0, // default creation flags
&ThreadID); // receive thread identifier
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return;
}
}
// Wait for all threads to terminate
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread and mutex handles
for( i=0; i < THREADCOUNT; i++ )
CloseHandle(aThread[i]);
CloseHandle(ghMutex);
printf("g_x is :%d\n",g_x);
}
DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{
DWORD dwCount=0, dwWaitResult;
// Request ownership of mutex.
while( dwCount < 100 )
{
dwWaitResult = WaitForSingleObject(
ghMutex, // handle to mutex
INFINITE); // no time-out interval
switch (dwWaitResult)
{
// The thread got ownership of the mutex
case WAIT_OBJECT_0:
__try {
g_x++;
// TODO: Write to the database
printf("Thread %d writing to database\n",
GetCurrentThreadId());
dwCount++;
}
__finally {
// Release ownership of the mutex object
if (! ReleaseMutex(ghMutex))
{
// Deal with error.
}
}
break;
// The thread got ownership of an abandoned mutex
case WAIT_ABANDONED:
return FALSE;
}
}
return TRUE;
}
多线程同步之Semaphore (主要用来解决生产者/消费者问题)
一 信标Semaphore
信标内核对象用于对资源进行计数。它们与所有内核对象一样,包含一个使用数量,但是它们也包含另外两个带符号的3 2位值,一个是最大资源数量,一个是当前资源数量。最大资源数量用于标识信标能够控制的资源的最大数量,而当前资源数量则用于标识当前可以使用的资源的数量。
为了正确地说明这个问题,让我们来看一看应用程序是如何使用信标的。比如说,我正在开发一个服务器进程,在这个进程中,我已经分配了一个能够用来存放客户机请求的缓冲区。我对缓冲区的大小进行了硬编码,这样它每次最多能够存放5个客户机请求。如果5个请求尚未处理完毕时,一个新客户机试图与服务器进行联系,那么这个新客户机的请求就会被拒绝,并出现一个错误,指明服务器现在很忙,客户机应该过些时候重新进行联系。当我的服务器进程初始化时,它创建一个线程池,里面包含5个线程,每个线程都准备在客户机请求到来时对它进行处理。
开始时,没有客户机提出任何请求,因此我的服务器不允许线程池中的任何线程成为可调度线程。但是,如果3个客户机请求同时到来,那么线程池中应该有3个线程处于可调度状态。使用信标,就能够很好地处理对资源的监控和对线程的调度,最大资源数量设置为5,因为这是我进行硬编码的缓冲区的大小。当前资源数量最初设置为0,因为没有客户机提出任何请求。当客户机的请求被接受时,当前资源数量就递增,当客户机的请求被提交给服务器的线程池时,当前资源数量就递减。
信标的使用规则如下:
• 如果当前资源的数量大于0,则发出信标信号。
• 如果当前资源数量是0,则不发出信标信号。
• 系统决不允许当前资源的数量为负值。
• 当前资源数量决不能大于最大资源数量。
当使用信标时,不要将信标对象的使用数量与它的当前资源数量混为一谈。
二 API
三 实例
#include <stdio.h>
#define MAX_SEM_COUNT 6
#define THREADCOUNT 12
HANDLE ghSemaphore;
DWORD WINAPI ThreadProc( LPVOID );
void main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
int i;
// Create a semaphore with initial and max counts of MAX_SEM_COUNT
ghSemaphore = CreateSemaphore(
NULL, // default security attributes
MAX_SEM_COUNT, // initial count
MAX_SEM_COUNT, // maximum count
NULL); // unnamed semaphore
if (ghSemaphore == NULL)
{
printf("CreateSemaphore error: %d\n", GetLastError());
return;
}
// Create worker threads
for( i=0; i < THREADCOUNT; i++ )
{
aThread[i] = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE) ThreadProc,
NULL, // no thread function arguments
0, // default creation flags
&ThreadID); // receive thread identifier
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return;
}
}
// Wait for all threads to terminate
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread and semaphore handles
for( i=0; i < THREADCOUNT; i++ )
CloseHandle(aThread[i]);
CloseHandle(ghSemaphore);
}
DWORD WINAPI ThreadProc( LPVOID lpParam )
{
DWORD dwWaitResult;
BOOL bContinue=TRUE;
while(bContinue)
{
// Try to enter the semaphore gate.
dwWaitResult = WaitForSingleObject(
ghSemaphore, // handle to semaphore
3L); // zero-second time-out interval
switch (dwWaitResult)
{
// The semaphore object was signaled.
case WAIT_OBJECT_0:
// TODO: Perform task
printf("Thread %d: wait succeeded\n", GetCurrentThreadId());
bContinue=FALSE;
// Simulate thread spending time on task
Sleep(5);
for(int x = 0; x< 10; x++)
printf("Thread %d task!\n",GetCurrentThreadId());
// Relase the semaphore when task is finished
if (!ReleaseSemaphore(
ghSemaphore, // handle to semaphore
1, // increase count by one
NULL) ) // not interested in previous count
{
printf("ReleaseSemaphore error: %d\n", GetLastError());
}
break;
// The semaphore was nonsignaled, so a time-out occurred.
case WAIT_TIMEOUT:
printf("Thread %d: wait timed out\n", GetCurrentThreadId());
break;
}
}
return TRUE;
}
多线程同步之Event(主要用来线程间的等待通知)
一 Event
在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。
Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用ResetEvent函数,因为系统会自动对事件进行重置。但是,Microsoft没有为人工重置的事件定义成功等待的副作用,所以需要调用ResetEvent()。
二 Event API
三 代码实例
1)使用手动的Event:当文件读入内存的时候,WordCount, SpellCheck,GrammarCheck可以同时进行,这里使用Event,当文件一读入内存就通知WordCount,SpellCheck和GrammarCheck线程开始执行。
#include <process.h>
#include <stdio.h>
// a global handle to event.
HANDLE g_hEvent;
void OpenFileAndReadContentsIntoMemory();
unsigned __stdcall WordCount(void *pvParam);
unsigned __stdcall SpellCheck(void *pvParam);
unsigned __stdcall GrammarCheck(void *pvParam);
int main()
{
//Create the manual-reset, nonsignaled event.
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//Spawn 3 new threads.
HANDLE hThread[3];
unsigned dwThreadID[3];
hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WordCount, NULL, 0, &dwThreadID[0]);
hThread[1] = (HANDLE)_beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID[1]);
hThread[2] = (HANDLE)_beginthreadex(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID[2]);
OpenFileAndReadContentsIntoMemory();
//Allow all 3 threads to access the memory.
SetEvent(g_hEvent);
printf("main thread exit\n");
return 1;
}
void OpenFileAndReadContentsIntoMemory()
{
printf("Open File and Read contents into memory\n");
}
unsigned __stdcall WordCount(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("0:word count\n");
return(0);
}
unsigned __stdcall SpellCheck(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("1:Spell check\n");
return(0);
}
unsigned __stdcall GrammarCheck(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("2:Grammar check\n");
return(0);
}
2)修改上面的代码,使用自动Event,则必须在3个子线程中增加SetEvent()。且要想让3个线程都执行完,必须的增加Waitfor()函数。
#include <process.h>
#include <stdio.h>
// a global handle to event.
HANDLE g_hEvent;
void OpenFileAndReadContentsIntoMemory();
unsigned __stdcall WordCount(void *pvParam);
unsigned __stdcall SpellCheck(void *pvParam);
unsigned __stdcall GrammarCheck(void *pvParam);
int main()
{
//Create the AUTO-reset, nonsignaled event.
g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
//Spawn 3 new threads.
HANDLE hThread[3];
unsigned dwThreadID[3];
hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WordCount, NULL, 0, &dwThreadID[0]);
hThread[1] = (HANDLE)_beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID[1]);
hThread[2] = (HANDLE)_beginthreadex(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID[2]);
OpenFileAndReadContentsIntoMemory();
//Allow all 3 threads to access the memory.
SetEvent(g_hEvent);
//wait for child threads to exit
DWORD dwCThd = WaitForMultipleObjects (3, //count of objects
hThread, //thread handle
TRUE, //wait for all
INFINITE); //time out interval
if(dwCThd != WAIT_OBJECT_0)
{
printf("error\n");
exit(-1);
}
//close handles
CloseHandle (g_hEvent);
//close child thread handles
for (int i=0; i<3; i++)
CloseHandle (hThread[i]);
printf("main thread exit\n");
return 1;
}
void OpenFileAndReadContentsIntoMemory()
{
printf("Open File and Read contents into memory\n");
}
unsigned __stdcall WordCount(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("0:word count\n");
SetEvent(g_hEvent);
return(0);
}
unsigned __stdcall SpellCheck(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("1:Spell check\n");
SetEvent(g_hEvent);
return(0);
}
unsigned __stdcall GrammarCheck(void *pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);
//Access the memory block.
printf("2:Grammar check\n");
SetEvent(g_hEvent);
return(0);
}
多线程之线程局部存储
一 线程局部存储 (TLS)
来自:http://msdn2.microsoft.com/en-us/library/ms686749.aspx
同一进程中的所有线程共享相同的虚拟地址空间。不同的线程中的局部变量有不同的副本,但是static和globl变量是同一进程中的所有线程共享的。使用TLS技术可以为static和globl的变量,根据当前进程的线程数量创建一个array,每个线程可以通过array的index来访问对应的变量,这样也就保证了static和global的变量为每一个线程都创建不同的副本。
二 线程局部存储(TLS)实现使用
1)TLS 的 API 实现
通过 Win32 API 层和编译器实现“线程本地存储”。有关详细信息,请参见 Win32 API 文档中的 TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree。 (下面的代码对msdn的稍加修改)
#include <stdio.h>
#define THREADCOUNT 10
DWORD dwTlsIndex;
static int g_x = 100; // test static var for multiple threading
VOID ErrorExit(LPSTR);
VOID CommonFunc(VOID)
{
LPVOID lpvData;
// Retrieve a data pointer for the current thread.
lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
ErrorExit("TlsGetValue error");
int *pg_x = (int*)lpvData;
// Use the data stored for the current thread.
printf("thread %d: g_x adress=%lx,g_x copy ++ = %d\n", GetCurrentThreadId(), pg_x, *pg_x);
Sleep(1000);
}
DWORD WINAPI ThreadFunc(VOID)
{
LPVOID lpvData;
// Initialize the TLS index for this thread.
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
//*(int*)lpvData = g_x;
int *pg_x = (int*)lpvData;
*pg_x = g_x;
if (! TlsSetValue(dwTlsIndex, lpvData))
ErrorExit("TlsSetValue error");
printf("thread %d: g_x adress=%lx,g_x copy = %d\n", GetCurrentThreadId(), pg_x, *pg_x);
InterlockedExchangeAdd(reinterpret_cast<long*>(pg_x),1);
CommonFunc();
// Release the dynamic memory before the thread returns.
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL) lpvData);
return 0;
}
int main(VOID)
{
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;
printf("main thread: g_x is :%d\n",g_x);
// Allocate a TLS index.
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
ErrorExit("TlsAlloc failed");
//test for multiple static or global var
int dwTlsIndex2 = TlsAlloc();
// Create multiple threads.
for (i = 0; i < THREADCOUNT; i++)
{
hThread[i] = CreateThread(NULL, // default security attributes
0, // use default stack size
(LPTHREAD_START_ROUTINE) ThreadFunc, // thread function
NULL, // no thread function argument
0, // use default creation flags
&IDThread); // returns thread identifier
// Check the return value for success.
if (hThread[i] == NULL)
ErrorExit("CreateThread error\n");
}
for (i = 0; i < THREADCOUNT; i++)
WaitForSingleObject(hThread[i], INFINITE);
TlsFree(dwTlsIndex);
printf("main thread: g_x is :%d\n",g_x);
return 0;
}
VOID ErrorExit (LPSTR lpszMessage)
{
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}
运行结果:(可以看出不同的线程中的g_x变量有不同的地址,即有不同的拷贝,主线程变量在开始和最后都不被改变。)
PS: (TLS的实现原理与API解释)
1:在多线程的进程中,为每一个static或是global变量创建一个void*的数组,使变量不同线程都有一个拷贝,然后拷贝的指针放入Void*的数组中。
2:TlsAlloc() 得到对应于一个static或global变量所对应的void*的数组的索引,这个用来标示不同static或global变量所对应的void*的数组。
3:LocalAlloc()用来分配变量在此线程的拷贝的指针。
4:TlsSetValue()用来把刚分配的指针加到所对应的void*的数组中,加入后一般对会对此指针赋值供此线程使用。
5:TlsGetValue()用来从对应的void*的数组中找到此线程所对应的拷贝的指针。
6: TlsFree() 释放整个void*的指针数组。
2)TLS 的编译器实现
为了支持 TLS,已将新属性 thread 添加到了 C 和 C++ 语言,并由 Visual C++ 编译器支持。使用 __declspec 关键字声明 thread 变量。例如,以下代码声明了一个整数线程局部变量,并用一个值对其进行初始化:
__declspec( thread ) int tls_i = 1;
下面的代码使用了VC提供的__declspec关键字来实现TLS,如果不使用TLS,全局变量g_x则在线程中输出的结果都是1,2,3,4.。。。但是如果使用TLS则在线程中输出的结果都是1,编译器帮我们保证了每个线程都有一个副本。 我们还可以看到主线程在开始和最后输出的g_x都是0,这也更说明了在线程有不同的副本。
#include <stdio.h>
#define THREADCOUNT 10
DWORD dwTlsIndex;
//static int g_x = 0;
#define Thread __declspec(thread)
Thread static int g_x = 0;
VOID ErrorExit(LPSTR);
DWORD WINAPI ThreadFunc(VOID)
{
InterlockedExchangeAdd(reinterpret_cast<long*>(&g_x),1); //g_x+=1;
printf("thread id: %d, g_x++ = %d, g_x adress = %d\n",GetCurrentThreadId(),g_x,&g_x);
return 1;
}
int main(VOID)
{
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;
printf("main thread: g_x = %d, g_x adress = %d\n",g_x,&g_x);
// Create multiple threads.
for (i = 0; i < THREADCOUNT; i++)
{
hThread[i] = CreateThread(NULL, // default security attributes
0, // use default stack size
(LPTHREAD_START_ROUTINE) ThreadFunc, // thread function
NULL, // no thread function argument
0, // use default creation flags
&IDThread); // returns thread identifier
// Check the return value for success.
if (hThread[i] == NULL)
ErrorExit("CreateThread error\n");
}
for (i = 0; i < THREADCOUNT; i++)
WaitForSingleObject(hThread[i], INFINITE);
printf("main thread: g_x = %d, g_x adress = %d\n",g_x,&g_x);
return 0;
}
VOID ErrorExit (LPSTR lpszMessage)
{
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
运行结果:
三 使用VC关键字实现TLS需要注意:
声明静态绑定线程的本地对象和变量时必须遵守下列原则:
- thread 属性只能应用于数据声明和定义。它不能用于函数声明或定义。例如,以下代码将生成一个编译器错误:
#define Thread __declspec( thread ) Thread void func(); // This will generate an error.
- 只能在具有 static 作用域的数据项上指定 thread 修饰符。包括全局数据对象(包括 static 和 extern)、本地静态对象和 C++ 类的静态数据成员。不可以用 thread 属性声明自动数据对象。以下代码将生成编译器错误:
#define Thread __declspec( thread ) void func1() { Thread int tls_i; // This will generate an error. } int func2( Thread int tls_i ) // This will generate an error. { return tls_i; }
- 线程本地对象的声明和定义必须全都指定 thread 属性。例如,以下代码将生成错误:
#define Thread __declspec( thread ) extern int tls_i; // This will generate an error, since the int Thread tls_i; // declaration and definition differ.
- thread 属性不能用作类型修饰符。例如,以下代码将生成一个编译器错误:
char __declspec( thread ) *ch; // Error
- C++ 类不能使用 thread 属性。但是,可以使用 thread 属性将 C++ 类对象实例化。例如,以下代码将生成一个编译器错误:
#define Thread __declspec( thread ) class Thread C // Error: classes cannot be declared Thread. { // Code }; C CObject;
因为允许使用 thread 属性的 C++ 对象的声明,因此下面两个示例在语义上是等效的:
#define Thread __declspec( thread ) Thread class B { // Code } BObject; // OK--BObject is declared thread local. class B { // Code }; Thread B BObject; // OK--BObject is declared thread local.
- 不将线程本地对象的地址视为常数,并且涉及此类地址的任何表达式都不视为常数。在标准 C 中,这种作法的效果是禁止将线程本地变量的地址用作对象或指针的初始值设定项。例如,C 编译器将以下代码标记为错误:
#define Thread __declspec( thread ) Thread int tls_i; int *p = &tls_i; //This will generate an error in C.
但是,此限制不适用于 C++。因为 C++ 允许动态初始化所有对象,因此可以用使用线程本地变量地址的表达式初始化对象。实现此操作的方式与实现线程本地对象结构的方式相同。例如,以上显示的代码在作为 C++ 源文件编译时不会生成错误。请注意:只有在其中获取地址的线程仍然存在的情况下,线程本地变量的地址才有效。
- 标准 C 允许使用涉及引用自身的表达式初始化对象或变量,但只适用于非静态作用域的对象。虽然 C++ 通常允许使用涉及引用自身的表达式动态初始化对象,但是这种类型的初始化不允许用于线程本地对象。例如:
#define Thread __declspec( thread ) Thread int tls_i = tls_i; // Error in C and C++ int j = j; // OK in C++, error in C Thread int tls_i = sizeof( tls_i ) // Legal in C and C++
请注意:包含正在初始化的对象的
sizeof
表达式不建立对自身的引用且在 C 和 C++ 中都是合法的。C++ 不允许此类对线程数据的动态初始化,因为将来可能要对线程本地存储功能进行增强。
- 如果 DLL 将任何非本地数据或对象声明为 __declspec(线程),动态加载该 DLL 时会导致保护错误。使用 LoadLibrary 加载所有 DLL 后,每当代码引用非本地 __declspec(线程)数据时,将导致系统故障。由于线程的全局变量空间是在运行时分配的,因此此空间的大小是以应用程序的需求和所有静态链接的 DLL 的需求相加为基础计算出来的。使用 LoadLibrary 时,无法扩展此空间以允许放置用 __declspec(线程)声明的线程本地变量。如果 DLL 可能是用 LoadLibrary 加载的,请在 DLL 中使用 TLS API(如 TlsAlloc)来分配 TLS。
多线程使用总结
基础:
1)最好使用C++ runtime的函数创建线程,即调用_beginthreadex创建线程。CreateThread()函数并不会执行C运行时数据块的变量的每线程初始化,因此在任何使用C运行时库的应用中,不能使用CrateThread()函数。
2)最好不要显示的调用ExitThread()或TerminateThread(),因为调用这些不进行清理工作。
3)SuspendThread()挂起线程时,要考虑该线程是否拥有Mutex或Semaphore,如果拥有的话可能会导致死锁。
4)信号量Semaphore,是一个可以限制对指定的临界段进行访问的线程的数目的数据结构。
5)互斥量Mutex和关键代码段CriticalSection,他们的作用是相同的,都是用来保证某时刻只有一个线程能够访问全局或静态的资源。区别是:Mutex是内核对象,可以设置等待超时时间,可以在不同的进程的线程中使用,但是所消耗的时间也比较多。CriticalSection与Mutex相反。
6)互锁函数,可以用来实现原子操作。对于一些简单的问题,比如说int变量的自加,交换。。。
7)线程局部存储(TLS),可以为全局或静态变量对不同的线程有不同的拷贝。
高级:
8)线程池,可以实现在程序中根据需要动态的创建线程,比如在server端,根据访问的用户的多少来创建线程的多少。在windows2000以后增加了创建线程池的API,比如 QueueUserWorkItem()。
9)线程的优先级,用来保证重要的事情先被完成。不能使用线程的优先级来解决线程的同步问题。
10) 处理器亲和,就是将线程固定在某cpu上执行,这样在某些情况下有助于提高性能,例如我们有双核的且支持超线程技术的cpu,我们有4个线程,有2个是IO操作,有2个是大量的计算,对于上面的问题,我们就可以使用处理器亲和,使用API设置,来达到cpu使用的均和,更好的提高性能。
11) 纤程,用户级的线程机制,比线程小的单位,开发人员负责管理纤程的调度,同时负责确定纤程何时在线程时间片上下文中运行,一般不会带来性能的提高,主要的目的是为开发人员调度多个不需要并行执行的任务提供一个便捷的机制。
12) 是否需要使用多线程,最总是需要测试来决定的,而且对于不同的CPU结果也不相同。避免使用过多的线程而带来性能下降。
其他多线程技术:
13) MFC多线程,多Windows多线程API的封装。
14) Boost多线程。
15) POSIX,Pthreads,是一个可以移植的多线程库。一般是Linux和Unix是商用较多。
16) Intel Open MP+,是一种可移植的多线程解决方案。(www.openmp.org)。
多线程调试:
17) 支持多线程的Log。
18) VS2005中,可以查看Thread窗口,挂起或恢复线程,切换当前的线程和检查线程状态。或是使用TracePoint来输出消息到output。
19)使用SetThreadName()来对线程命名,使用线程信息block (http://www.codeproject.com/threads/xtib.asp)。
20) 使用Intel多线程线程检测器,Intel调试器。。。
一 线程
1)如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用VisualC++运行期库函数_beginthreadex,退出也应该使用_endthreadex。如果不使用Microsoft的VisualC++编译器,你的编译器供应商有它自己的CreateThred替代函数。不管这个替代函数是什么,你都必须使用。
2)因为_beginthreadex和_endthreadex是CRT线程函数,所以必须注意编译选项runtimelibaray的选择,使用MT或MTD。
3) _beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同。这是因为Microsoft的C/C++运行期库的开发小组认为,C/C++运行期函数不应该对Windows数据类型有任何依赖。_beginthreadex函数也像CreateThread那样,返回新创建的线程的句柄。
4)下面是关于_beginthreadex的一些要点:
•每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的VisualC++源代码中)。
•传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。
•_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
•当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
•如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。
5) _endthreadex的一些要点:
•C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。
•然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。
6) 虽然也提供了简化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。
6)线程handle因为是内核对象,所以需要在最后close handle。
7)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。
8)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。
二 线程同步之Critical Sections
1) 因为Critical Sections不是内核对象,所以只能用来同一进程内线程间的同步,不能用来多个不同进程间的线程的同步。
2) 如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则结果是该线程所对应的内核不能被释放,该线程成为死线程。
3) 要比其他的内核对象的速度要快。
4)很好的封装:
class CritSect
{
public:
friend class Lock;
CritSect() { InitializeCriticalSection(&_critSection); }
~CritSect() { DeleteCriticalSection(&_critSection); }
private:
void Acquire(){EnterCriticalSection(&_critSection);}
void Release(){LeaveCriticalSection(&_critSection);}
CRITICAL_SECTION _critSection;
};
class Lock
{
public:
Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }
~Lock(){_critSect.Release();}
private:
CritSect& _critSect;
};
调用:CritSect sect;Lock lock(sect);
三 线程同步之Mutex
1)互斥对象(mutex)内核对象能够确保线程拥有对单个资源的互斥访问权。实际上互斥对象是因此而得名的。互斥对象包含一个使用数量,一个线程ID和一个递归计数器。
2) 互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。这意味着互斥对象的运行速度比关键代码段要慢。但是这也意味着不同进程中的多个线程能够访问单个互斥对象,并且这意味着线程在等待访问资源时可以设定一个超时值。
3) ID用于标识系统中的哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对象的次数。
4) 互斥对象有许多用途,属于最常用的内核对象之一。通常来说,它们用于保护由多个线程访问的内存块。如果多个线程要同时访问内存块,内存块中的数据就可能遭到破坏。互斥对象能够保证访问内存块的任何线程拥有对该内存块的独占访问权,这样就能够保证数据的完整性。
5)互斥对象的使用规则如下:
• 如果线程ID是0(这是个无效ID),互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。
• 如果ID是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。
• 与所有其他内核对象不同, 互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则。
四 线程同步之Event
1)在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
2)事件能够通知一个操作已经完成。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
3)当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。
4)Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用ResetEvent函数,因为系统会自动对事件进行重置。但是,Microsoft没有为人工重置的事件定义成功等待的副作用,所以需要调用ResetEvent()。
五 线程同步之信号量(Semaphore)
信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、 WaitForSingleObject()和WaitForMultipleObjects()等函数。
六 线程同步之其他
1)线程局部存储 (TLS),同一进程中的所有线程共享相同的虚拟地址空间。不同的线程中的局部变量有不同的副本,但是static和globl变量是同一进程中的所有线程共享的。使用TLS技术可以为static和globl的变量,根据当前进程的线程数量创建一个array,每个线程可以通过array的index来访问对应的变量,这样也就保证了static和global的变量为每一个线程都创建不同的副本。
2)互锁函数的家族十分的庞大,例如InterlockedExchangeAdd()。。。,使用互锁函数的优点是:他的速度要比其他的CriticalSection,Mutex,Event,Semaphore快很多。
3)等待函数,例如WaitForSingleObject 函数用来检测 hHandle 事件的信号状态,当函数的执行时间超过 dwMilliseconds 就返回,但如果参数 dwMilliseconds 为 INFINITE 时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到 WaitForSingleObject 有返回直才执行后面的代码。
- C语言的多线程编程
- Linux下的C多线程编程
- Linux下的C多线程编程
- Linux下的C多线程编程
- Windows C 多线程编程的库支持
- Linux下的C语言多线程编程
- Linux下的C语言多线程编程
- [我眼中的C#]多线程编程
- Linux下C语言的多线程编程
- linux下c语言的多线程编程
- c语言多线程编程
- C#:多线程编程探索
- linux c 多线程编程
- Linux C多线程编程
- C/C++ 多线程编程
- C语言多线程编程
- Linux C 多线程编程
- linux-C编程-多线程
- 一个完整的maven配置selenium webdriver工程实例(四)
- 利用DataTable进行删除GridView中的数据方法之新思路
- eclipse错误整理
- 从零开始
- 斗地主规则
- c++的多线程编程
- 一个完整的maven配置selenium webdriver工程实例(五)
- javaweb起航
- [生活大杂烩-4] 养老延长后的趣事!!
- 软件项目角色
- 在windows7的VS2005下windows media player 控件的使用问题
- 字符串数字转换为整型数字,考虑正负号,溢出,非法字符
- 强类型与弱类型 强引用与弱引用
- froyo系统原生bug及修正:改变系统语言时Launcher2的AllApps内shortcut不刷新[转载]