2010年9月27日星期一

Windows多线程编程

前面写了一篇Linux多线程编程,由于算法要整合到一个运行在Windows平台下的系统中,所以需要改成Windows下的多线程方式(看我折腾的)。这里简单列一个Windows多线程编程的例子。其实我也想尝试是用一个跨平台的C++库BoostBoost::Thread类库可以在不同的平台下运行多线程,而且多线程功能非常丰富和强大,使用起来也很容易,但是还需要安装这个库,先暂时放弃这个方案,因为毕竟我现在需要的多线程功能非常简单。

Windows API实现多线程

在上一篇文章中,已经列出了一个表,列出了Linux线程API和Windows下线程API的大致对应关系。根据那个表,和Linux下多线程经验,也能大概知道Windows下实现多线程的方法首先来看一个例子:

/* example.c */
#include <stdfx.h>
#include <stdio.h>
#include <process.h>

const int NLOOP = 100;
int counter = 0;
void thread(void*);
CRITICAL_SECTION beswap ;
int main()
{
HANDLE pnt[2];
InitializeCriticalSection(&beswap);
pnt[0] = (HANDLE)_beginthread(doit,0,NULL);
pnt[1] = (HANDLE)_beginthread(doit,0,NULL);
WaitForMultipleObjects( 2, pnt, TRUE, 1000L);
DeleteCriticalSection(&beswap);
return 0;
}
void doit(void*)
{
    printf("go...\n");
    int i, val = 0;
    for(i = 0; i < NLOOP; ++i)
    {
        EnterCriticalSection(&beswap);
        val = counter;
         printf("%d\n",val+1);
        counter = val + 1;
        LeaveCriticalSection(&beswap);
    }
    printf("end...\n");
    return ;
}

根据这个例子,多线程的过程解释如下:

1、编写线程函数,这里线程函数要遵循如下函数原型:

DWORD WINAPI threadFunc(LPVOID lpvThreadParm);

函数的输入参数是一个DWORD的类型,可以是一个整数,也可以是一个内存指针,具体的意义由编程者自己决定。返回值是一个DWORD型的值。

2、创建一个线程

一个进程的主线程是由操作系统自动生成,如果你要让一个主线程创建额外的线程,你可以调用来CreateThread完成,使用的时候注意包含#include<process.h>头文件。其函数原型如下:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpsa,DWORD cbstack,LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParm, DWORD fdwCreate,LPDWORD lpIDThread);

     其中,lpsa参数为一个指向SECURITY_ATTRIBUTES结构的指针。如果想让对象为缺省安全属性的话,可以传一个NULL,如果想让任一个子进程都可继承一个该线程对象句柄,必须指定一个SECURITY_ATTRIBUTES结构,其中bInheritHandle成员初始化为TRUE。
    参数cbstack表示线程为自己所用堆栈分配的地址空间大小,0表示采用系统缺省值。 
    参数lpStartAddr用来表示新线程开始执行时代码所在函数的地址,即为线程函数。
    lpvThreadParm为传入线程函数的参数,
    fdwCreate参数指定控制线程创建的附加标志,可以取两种值。如果该参数为0,线程就会立即开始执行,如果该参数为CREATE_SUSPENDED,则系统产生线程后,初始化CPU,登记CONTEXT结构的成员,准备好执行该线程函数中的第一条指令,但并不马上执行,而是挂起该线程。
    最后一个参数lpIDThread 是一个DWORD类型地址,返回赋给该新线程的ID值。

此外,还可以使用_beginthread等函数来创建线程,正如本例子中使用的方法:

handle=(HANDLE)_beginthread(threadFunc,0, NULL);

函数_beginthread的函数原型如下:

uintptr_t _beginthread(
  void( *start_address )( void * ),
  unsigned stack_size,
  void *arglist
  );

start_address新线程的起始地址,指向新线程调用的函数的起始地址;stack_size stack_size 新线程的堆栈大小,可以为0;arglist arglist 传递给线程的参数列表,无参数是为NULL。

3、终止线程,如果某线程调用了ExitThread 函数,就可以终止自己。

VOID ExitThread(UINTfuExitCode );

这个函数为调用该函数的线程设置了退出码fuExitCode后, 就终止该线程。调用TerminateThread函数亦可终止线程。

BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);

该函数用来结束由hThread参数指定的线程, 并把dwExitCode设成该线程的退出码。当某个线程不在响应时,我们可以用其他线程调用该函数来终止这个不响应的线程。

4、另外还有设置程序的优先级,挂起和恢复线程之类的。这里就不详述,因为还没有用到。

BOOL SetThreadPriority(HANDLE hThread,intnPriority); // 设置线程优先级
DWORD ResumeThread(HANDLEhThread); // 恢复线程
DWORD SuspendThread(HANDLE hThread); // 挂起线程

5、这里还用到了线程的阻塞函数WaitForMultipleObjects,原型:

DWORD WaitForMultipleObjects(
  DWORD nCount,
  const HANDLE* lpHandles,
  BOOL bWaitAll,
  DWORD dwMilliseconds
  );

WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象。当WaitForMultipleObjects等到多个内核对象的时候,如果它的bWaitAll 参数设置为false。其返回值减去WAIT_OBJECT_0 就是参数lpHandles数组的序号。如果同时有多个内核对象被触发,这个函数返回的只是其中序号最小的那个。如果为TRUE 则等待所有信号量有效在往下执行。(FALSE 当有其中一个信号量有效时就向下执行)。

对应的还有一个函数WaitForSingleObject,用来等待指定的内核对象。

6、还有互斥锁的使用方法,在例子中也有很清楚的使用方法。单进程的线程可以使用这种临界资源对象来解决同步互斥问题,该对象不能保证哪个个线程能够获得到临界资源对象,因而该系统能公平的对待每一个线程。

MFC实现多线程

MFC对Windows API进行了封装,可以说使用起来更加简单,而且提供了很多多线程的扩张功能。MFC实现多线程有两种,一种是工作者线程,另一种是用户界面线程。前一种是简单的线程,后一种是带有消息队列的线程。
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);

CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);

这是一个重载函数,前一个用来创建一个工作线程,后一个用来创建界面线程。MFC框架还提供了很多同步类,来实现线程间的同步和通信,具体的使用方法可以参考MSDN,由于还没有用到,等用到了,再详细总结。另外,在编程中,我也是用WaitForMultipleObjects来等待了AfxBeginThread创建的线程,而没有出现问题。

参考地址:

http://wujblog.appspot.com/2010/09/26/linux-multi-thread.html

http://www.wangchao.net.cn/bbsdetail_69676.html

http://blog.csdn.net/zjl_1026_2001/archive/2008/03/18/2193626.aspx

http://msdn.microsoft.com/en-us/library/s3w9x78e(VS.80).aspx

http://msdn.microsoft.com/zh-cn/library/975t8ks0(v=VS.80).aspx


2010年9月26日星期日

Linux多线程编程

在背景差分算法的实现过程中,由于要达到实时性的要求,就想用多线程来实现,进行并行计算,充分利用CPU的多核。之前并没有写过多线程的程序,在网上搜索关键字“C++多线程编程”,后来才知道,多线程并不是C++语言的特性,而是与平台有关系,每个平台实现的机制也是不一样的,所以说这种“C++多线程编程“提法就是有问题的。闲话少叙,下面言归正传。

线程的基本概念


线程的基本概念,在网上有很多的介绍,我觉得北大BBS上一个精华帖介绍的比较详细,在这里,线程相关的重要概念有:用户级线程、轻进程(LWP, Lightweight Process), 非绑定线程(Unbound Treads),绑定线程(Bound Thread)。对于每个线程有相关的属性和状态。有了多线程,就带来相关的数据访问同步的问题,有互斥锁,条件锁,信号量等等。


Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明(这段话是抄过来的,clone()还真的不知道是什么,到时候查一下)。


多线程开发在 Linux 平台上已经有成熟的 Pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。下面有一个表,转载自这里(这篇文章严谨的介绍了Linux上线程编程的相关经验)


表 1. 线程函数列表











































































对象 操作 Linux Pthread API Windows SDK 库对应 API
线程 创建 pthread_create CreateThread
退出 pthread_exit ThreadExit
等待 pthread_join WaitForSingleObject
互斥锁 创建 pthread_mutex_init CreateMutex
销毁 pthread_mutex_destroy CloseHandle
加锁 pthread_mutex_lock WaitForSingleObject
解锁 pthread_mutex_unlock ReleaseMutex
条件 创建 pthread_cond_init CreateEvent
销毁 pthread_cond_destroy CloseHandle
触发 pthread_cond_signal SetEvent
广播 pthread_cond_broadcast SetEvent / ResetEvent
等待 pthread_cond_wait / pthread_cond_timedwait SingleObjectAndWait


简单的多线程例子


这个表列出了Linux多线程API,作为一个多线程编程的入门,我是参考这里,这个文章对上面每个API的使用方法都有详细的介绍,并且配有相关的例子,是入门很好的资料,但是遗憾的是,他的这些例子,我并没有编译通过,有些地方需要改动一下。下面我也摘抄一部分到这里(做了一点点的修改,原文有一些错误),说先看到一个最简单的多线程的例子:



/* example1.c*/
#include <stdio.h>
#include <pthread.h>
void* thread(void* param)
{
    int i;
    for(i=0;i<3;i++)
        printf("This is a pthread.\n");
}

int main(void)
{
    pthread_t id;
    int i,ret;
    ret=pthread_create(&id,NULL, thread,NULL);
    if(ret!=0){
        printf ("Create pthread error!\n");
        exit (1);
    }
    for(i=0;i<3;i++)
        printf("This is the main process.\n");
    pthread_join(id,NULL);
    return (0);
}



编译上面的程序使用如下命令:



g++ example1.c -lpthread -o example1




顺便提一下,原文上是使用gcc命令,但是我在编译的时候得到了一个错误:undefined reference to `__gxx_personality_v0',网上有人说使用g++编译程序就能解决,原因是gcc不会帮你链接c++的运行库,但g++会。挺有道理的,似懂非懂。



这样,第一个多线程就能运行了,这里用到了pthred_t 类型,用来标示一个线程它在本质上一个无符号的长整型。函数pthread_create用来创建一个线程,注意线程创建完成就立即运行。此函数的原型是:



extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, void *(*__start_routine) (void *), void *__arg));



第一个参数是指向线程标识符的指针;第二个参数是一个指向线程属性类型(pthread_attr_t)的指针,用来设置线程的相关属性;第三个参数是线程运行函数起始地址,这个函数需要有这样的原型void* (*) (void*);最后一个参数是运行函数的参数。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。


这个例子中还有一个函数pthread_join,其原型如下:



extern int pthread_join __P ((pthread_t __th, void **__thread_return));



第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。其函数的原型如下:



extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));



唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

线程的属性设置


在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。


属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。


关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。


设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。例子如下:



#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, my_function, NULL);



线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。具体怎么用,还是参考这个原文吧,不然就把原文全部抄袭过来了。


参考地址:


http://fanqiang.chinaunix.net/a4/b8/20010811/0905001105.html


http://fanqiang.chinaunix.net/a4/b8/20010811/0905001105.html


http://www.fegensoft.com/fegensoft2002/seeksilence/Linux/10/8/index.htm


http://blog.sina.com.cn/s/blog_6b2757530100l639.html

2010年9月22日星期三

在OpenCV中,cvCreateFileCapture函数返回NULL

在OpenCV中,这个函数cvCreateFileCapture用来从视频文件(.avi)获取一个Capture,函数的原型如下:

Capture* cvCreateFileCapture(const char* filename)

在调用的时候发现返回值是NULL。在网上找到解决方法,主要原因还是解码器的问题。即使你的电脑能播放avi文件,但是cvCreateFileCapture只是支持有限的几种avi格式。

解决方法:网上下载安装K-Lite Code Pack解码器,一般就能解决问题。如果还是不行,就要把avi文件转换成Opencv支持的avi格式之一。 OpenCV支持的AVI如下:

Container

FourCC

Name

Description

AVI

'DIB '

RGB(A)

Uncompressed RGB, 24 or 32 bit

AVI

'I420'

RAW I420

Uncompressed YUV, 4:2:0 chroma subsampled

AVI

'IYUV'

RAW I420

identical to I420


本文参考了这里:

2010年9月12日星期日

最大类间方差法(Otsu法)

此方法的目的是把一幅图像,通过一个阈值T的方法,分割成前景和背景两部分。通过选定阈值T,是图像的前景部分和背景部分的差别最大。

其中,前景和背景的差别的衡量标准为如下表达式:g=wf*(uf-u)^2+wb*(ub-u)^2=wf*wb*(uf-ub)^2,其中wf和wb分别为前景和背景所占的比例,uf和ub为前景和背景的平均灰度。Otsu方法,就是寻找一个阈值T,把图像分割前前后景两部分,使前面的公式中g取得最大值。

计算阈值T的算法如下:首先统计一下图像的直方图,或者这里可以在做一下直方图平滑,然后设置T=0到最大值,逐次搜索每个值,得到一个最大的g的阈值T。

类间方差法对噪声和目标大小十分敏感,它仅对类间反差为单峰的图像产生较好的分割效果。当目标与背景的大小比例悬殊时,类间方差准则函数可能呈现双峰或者多峰,此时类间方差法效果就不太适用了。