【好久没写文章了,因为在忙一个项目,这个项目需要编写跨平台(windows+Linux)代码,所以对跨平台的东西做了些关注,本文及下来几篇文章都是针对跨平台的。】
如果一个项目需要跨平台,可选择的编程语言就受到一定的限制,如果再考虑运行效率,可选择的语言似乎只能是C/C++了。与Java不同,C/C++是被操作系统“原生态”支持的,因而各操作系统实现的方式也略有不同,各自做了不同的扩展,而不象Java一样有统一库函数及接口。
用C/C++做跨平台开发需要考虑操作系统的不同,当然,有别人写好的跨平台库,这当中,最著名的就是boost了。本文不是讨论boost的文章,笔者认为,如果开发难度不大,自己开发也不错,可以充分了解各操作系统的特点,因此,本文涉及的是自己写一些简单的跨平台的东西。
因为项目的性质,需要多线程,因此首先需要解决的是多线程方面的编程,这里,涉及两方面内容:
1.线程的创建(启动)与停止。
2.全局数据读写的同步(即互斥量的应用)。
一、Windows下的线程函数和互斥量
Windows下的线程函数有多种表达(这跟windows的发展历史有关或是跟windows的开发接口习惯有关,Windows的开发接口总是形式多样,以下互斥量也是如此)。MFC中有AfxCreateThread,AfxEndThread等函数,Win32API中有CreateThread,ExitThread等,随VC一起的有_beginThread和_endThread等。直接使用Win32 API中的线程函数,但又在在线程中调用某些CRT中的函数,就会有一点小的内存泄露(http://support.microsoft.com/kb/104641/),因此建议采用_beginThread、_endThread来创建和中止线程(本文不考虑用MFC库)。
Windows下的线程或进程间同步的手段大体上有 event, mutex, semaphore, timer object这几类。
event:一个线程等待信号,另一个线程发出信号。
semaphore:一个线程或多个线程占有资源,其它线程等待,即某个资源同时只能被n个线程占有,第n+1个线程只能等待。
mutex:是semaphore的一个特殊情况,一个线程占有资源,另外的线程等待资源。
大部分情况下,mutex已能满足我们的要求,所以本文也只涉及了mutex。
二、Linux下的线程函数和互斥量
Linux下的线程函数为pthread_create和pthread_exit,比较简单,需要注意的是,线程入口函数略有不同,pthread_create要求的线程函数为 void * (* proc)( void * ),而_beginThread要求的线程函数为void (* proc)( void * ),即返回值不同,为统一化,只能舍弃Linux下的线程函数的返回值。好在如果需要线程函数的返回值,可以定义一个全局变量来解决此问题。
Linux下的线程同步手段比较单一,有mutex和条件变量。互斥量操的函数为pthread_mutex_init,pthread_mutex_destroy等,条件变量类似于event,但需要与互斥量共同作用(如果是微软,可能会将二者合起来,这是微软一贯的风格)。同样需要注意的是,Linux中的互斥量没有名称,也就是它只能采用全局变量这种方式,并且不能运用到进程,而windows下的互斥量可以是命名互斥量,也可用于进程间同步。
Linux和Windows另一个不同是等待函数,Windows下的等待函数有两个:WaitForSingleObject和WaitForMultipleObjects,分别用于等待一个同步量和多个同步量,并有“超时”的概念,Linux下的等待函数有pthread_mutex_lock和pthread_mutex_trylock,只能等待一个同步量,没有“超时”的概念,为实现“超时”,本文采用一个简单的办法(见下面的实现)。
从Windows和Linux编程接口函数的比较,可以看出微软的接口确实比较“贴心”,但有时也会造成接口函数过多,刚开始时无所适从,例如C#的线程同步,方式多得使人眼花。
三、线程辅助程序代码
说明文件(lightthread.h)代码。
#ifndef _LIGHTTHREAD_H_
#define _LIGHTTHREAD_H_
#include "platform.h"
#if defined(_WIN32_PLATFORM_)
typedef void * Mutex_Handle;
#endif
#if defined(_LINUX_PLATFORM_)
typedef pthread_mutex_t Mutex_Handle;
#endif
class CLightThread
{
public:
CLightThread() {};
~CLightThread() {};
static int CreateThread(void ( *proc )( void * ), void *pargs);
static void EndThread();
static unsigned int GetCurrentThreadId();
static void Sleep(unsigned int milliseconds);
static void DiscardTimeSlice();
};
class CLightThreadMutex
{
public:
CLightThreadMutex();
~CLightThreadMutex();
int Lock();
int TryLock(unsigned int dwMilliseconds);
void Unlock();
private:
Mutex_Handle m_hMutex;
};
typedef struct
{
unsigned int errorno;
char errormsg[512];
} thread_error_t;
class CThreadError
{
typedef struct
{
thread_error_t threaderror;
unsigned int threadid;
void * next;
} internal_thread_error_t;
public:
CThreadError();
~CThreadError();
void operator=(int errorno);
void operator=(const char * msg);
void operator=(thread_error_t & st);
unsigned int GetLastErrorNo();
const char *GetLastErrorMsg();
const thread_error_t *GetLastErrorStruct();
private:
internal_thread_error_t* m_pStart;
internal_thread_error_t* allocMemory(unsigned int tid);
internal_thread_error_t * search(unsigned int tid);
};
#endif
实现文件(lightthread.cpp)代码。
#include
#include
#include
#include "lightthread.h"
#if defined(_WIN32_PLATFORM_)
#include
#include /* _beginthread, _endthread */
#define gxstrcpy(d,n,s) strcpy_s(d,n,s)
#endif
#if defined(_LINUX_PLATFORM_)
#include
#include
#include
#include
#define gxstrcpy(d,n,s) strncpy(d,s,n)
#define THREAD_IDLE_TIMESLICE_MS 20
#endif
#define GX_UNDEFINED 0xffffffff
#define GX_S_OK 0x00000000
#include "TimeSpan.h"
#if defined(_LINUX_PLATFORM_)
typedef struct
{
void (* proc)( void * ) ;
void * pargs;
} _threadwraper_linux_t;
void * _ThreadWraper_Linux(void *pargs)
{
_threadwraper_linux_t *pth= (_threadwraper_linux_t *)pargs;
pth->proc(pth->pargs);
delete[] pth;
return NULL;
}
int CLightThread::CreateThread(void ( *proc )( void * ), void *pargs)
{
pthread_t ntid;
_threadwraper_linux_t* pthreadwraper = new _threadwraper_linux_t[1];
pthreadwraper[0].proc = proc;
pthreadwraper[0].pargs = pargs;
return pthread_create(&ntid, NULL, _ThreadWraper_Linux, pthreadwraper);
}
void CLightThread::DiscardTimeSlice()
{
usleep(THREAD_IDLE_TIMESLICE_MS*1000);
}
void CLightThread::EndThread()
{
pthread_exit(NULL);
}
unsigned int CLightThread::GetCurrentThreadId()
{
return pthread_self();
}
void CLightThread::Sleep(unsigned int milliseconds)
{
if(milliseconds>=1000)
{
unsigned int s = milliseconds/1000;
unsigned int us = milliseconds - s*1000;
sleep(s);
if(us>0) usleep(us*1000);
}
else
{
usleep(milliseconds*1000);
}
}
//=====================================================================================
CLightThreadMutex::CLightThreadMutex()
{
pthread_mutex_init(&m_hMutex, NULL);
}
CLightThreadMutex::~CLightThreadMutex()
{
pthread_mutex_destroy(&m_hMutex);
}
int CLightThreadMutex::Lock()
{
return pthread_mutex_lock(&m_hMutex) == 0 ?0:-1;
}
int CLightThreadMutex::TryLock(unsigned int dwMilliseconds)
{
// The function pthread_mutex_trylock() returns zero if a lock on the mutex object referenced by mutex is acquired. Otherwise, an error number is returned to indicate the error.
unsigned int us= dwMilliseconds*1000;
int rt = pthread_mutex_trylock(&m_hMutex);
if( rt == EBUSY)
{
CMyTimeSpan start;
while(rt == EBUSY)
{
if( start.GetSpaninMilliseconds()>dwMilliseconds)
{
rt = -1;
}
else
{
usleep(20000); //sleep 20ms
rt = pthread_mutex_trylock(&m_hMutex);
}
}
}
return rt;
}
void CLightThreadMutex::Unlock()
{
pthread_mutex_unlock(&m_hMutex);
}
#endif
#if defined(_WIN32_PLATFORM_)
int CLightThread::CreateThread(void( *proc )( void * ), void *pargs)
{
return _beginthread( proc, 0, pargs );
}
void CLightThread::EndThread()
{
_endthread();
}
unsigned int CLightThread::GetCurrentThreadId()
{
return ::GetCurrentThreadId();
}
void CLightThread::Sleep(unsigned int miniseconds)
{
::Sleep(miniseconds);
}
void CLightThread::DiscardTimeSlice()
{
::SwitchToThread();
}
//=====================================================================================
//=====================================================================================
CLightThreadMutex::CLightThreadMutex()
{
m_hMutex = CreateMutexA(NULL,FALSE,NULL);
}
CLightThreadMutex::~CLightThreadMutex()
{
if(m_hMutex) CloseHandle(m_hMutex);
}
int CLightThreadMutex::Lock()
{
if( m_hMutex && WaitForSingleObject(m_hMutex, INFINITE)==WAIT_OBJECT_0) return 0;
return -1;
}
int CLightThreadMutex::TryLock(unsigned int dwMilliseconds)
{
if( m_hMutex&& WaitForSingleObject(m_hMutex, dwMilliseconds) ==WAIT_OBJECT_0) return 0;
return -1;
}
void CLightThreadMutex::Unlock()
{
if(m_hMutex) ReleaseMutex(m_hMutex);
}
#endif
//=====================================================================================================
CThreadError::CThreadError()
{
m_pStart = NULL;
}
CThreadError::~CThreadError()
{
internal_thread_error_t *temp;
while(m_pStart)
{
temp = m_pStart;
m_pStart = (internal_thread_error_t*)m_pStart->next;
delete temp;
}
}
void CThreadError::operator=(int errorno)
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
if(!temp)
{
temp = allocMemory(tid);
}
temp->threaderror.errorno = errorno;
temp->threaderror.errormsg[0] = '\0';
}
void CThreadError::operator=(const char * msg)
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
if(!temp)
{
temp = allocMemory(tid);
}
temp->threaderror.errorno = GX_UNDEFINED;
gxstrcpy(temp->threaderror.errormsg, 510, msg);
}
void CThreadError::operator=(thread_error_t & st)
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
if(!temp)
{
temp = allocMemory(tid);
}
memcpy(&temp->threaderror, &st, sizeof(thread_error_t));
}
unsigned int CThreadError::GetLastErrorNo()
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
return temp?temp->threaderror.errorno:GX_S_OK;
}
const char *CThreadError::GetLastErrorMsg()
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
return temp?(const char*)temp->threaderror.errormsg:NULL;
}
const thread_error_t *CThreadError::GetLastErrorStruct()
{
unsigned int tid = CLightThread::GetCurrentThreadId();
internal_thread_error_t *temp = search(tid);
return temp?(const thread_error_t *)(&(temp->threaderror)):NULL;
}
CThreadError::internal_thread_error_t* CThreadError::allocMemory(unsigned int tid)
{
internal_thread_error_t *temp = new internal_thread_error_t;
temp->threadid = tid;
temp->next = m_pStart;
m_pStart = temp;
return temp;
}
CThreadError::internal_thread_error_t * CThreadError::search(unsigned int tid)
{
internal_thread_error_t *temp = m_pStart;
while(temp)
{
if(temp->threadid == tid) break;
temp->next = (void *)temp;
}
return temp;
}
上面代码中,在不同的平台下,需要定义一个宏,_LINUX_PLATFROM_或_WIN32_PLATFROM_,对于Linux和Windows平台。CThreadError是一个线程错误类,保存了当前线程的最后一次错误值和描述。Linux版线程实现函数的TryLock中,用到一个CMyTimeSpan类,它的作用是计时,其实现代码如下:
#include "platform.h"
#if defined(_WIN32_PLATFORM_)
#include
#define timelong_t ULARGE_INTEGER
#endif
#if defined(_LINUX_PLATFORM_)
#include
#include
#define timelong_t struct timeval
#endif
class CMyTimeSpan
{
public:
CMyTimeSpan();
void Reset();
unsigned long long GetSpaninMicroseconds();
unsigned int GetSpaninMilliseconds();
unsigned int GetSpaninSeconds();
private:
timelong_t m_start;
void getCurrentTimeLong(timelong_t *tl);
};
//=====================================================================================
CMyTimeSpan::CMyTimeSpan()
{
getCurrentTimeLong(&m_start);
}
void CMyTimeSpan::Reset()
{
getCurrentTimeLong(&m_start);
}
unsigned int CMyTimeSpan::GetSpaninMilliseconds()
{
return (unsigned int)(GetSpaninMicroseconds()/1000LL);
}
unsigned int CMyTimeSpan::GetSpaninSeconds()
{
return (unsigned int)(GetSpaninMicroseconds()/1000000LL);
}
unsigned long long CMyTimeSpan::GetSpaninMicroseconds()
{
timelong_t end;
getCurrentTimeLong(&end);
#if defined(_WIN32_PLATFORM_)
return (end.QuadPart - m_start.QuadPart)/10;
#endif
#if defined(_LINUX_PLATFORM_)
return 1000000LL * ( end.tv_sec - m_start.tv_sec ) + end.tv_usec - m_start.tv_usec;
#endif
}
void CMyTimeSpan::getCurrentTimeLong(timelong_t *tl)
{
#if defined(_WIN32_PLATFORM_)
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
tl->HighPart= ft.dwHighDateTime;
tl->LowPart = ft.dwLowDateTime;
#endif
#if defined(_LINUX_PLATFORM_)
gettimeofday( tl, NULL);
#endif
}