多线程编程入门介绍

-----俺本菜鸟,只是最近程序中要用到多线程操作,看到这篇文章觉得对一些新手很有帮助,所以就把它翻译过来了。希望对广大编程新手有所帮助,如果有译得不妥之处,还望各位不吝板砖。

最近有人问我工作线程同步的问题,我推荐通过设置事件来实现。此人回应道:你不能那样做,因为因为工作线程不支持消息泵(UI线程要求支持消息)。看来对此的困惑主要是大家对事件和消息的误解,这是两个Windows下不同的生命体。

我已经记不清我最初是从哪儿拷贝这些例子的,由于这些例子很简单我发现它们很有意思。如果有谁知道这些代码的作者,请写信给写,我会非常乐意把荣誉授予他们。

要注意的是MFC对给予线程操作相当大的支持,但是我们在这儿不能一概而论。在一个MFC应用程序中,象_beginthread (一个C支持库调用)这样的API函数很可能被AfxBeginThread这样的MFC API函数所取代。

不同步

第一个例子主要说明两个不同步的线程。主循环,也就是进程的主线程,打印出一个全局整数数组的内容。名称为"Thread"的线程则持续地增加这个全局整数数组。 

  #include
  #include
 
  int a[ 5 ];
 
  void Thread( void* pParams )
  { int i, num = 0;
 
    while ( 1 )
    {
       for ( i = 0; i < 5; i++ ) a[ i ] = num;
       num++;
    }
  }
 
  int main( void )
  {
     _beginthread( Thread, 0, NULL );
 
     while( 1 )
        printf("%d %d %d %d %d ",
               a[ 0 ], a[ 1 ], a[ 2 ],
               a[ 3 ], a[ 4 ] );
 
   return 0;
  }

注意,在这个例子的输出中,红色的数字说明了这种情况:主线程抢占了次线程,在值正在增加的数组中间。

81751652 81751652 81751651 81751651 81751651
81751652 81751652 81751651 81751651 81751651
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629

如果你正在跑Windows 9x/NT/2000系统,你能够这些程序。在程序运行后,按“pause”键停止显示输出(这会暂停主线程的I/O,但是次线程会在后台继续运行),任意键重新开始它。

临界区对象

如果你的主线程在读之前需要处理数组的所有元素呢?方法是使用临界区

临界区对象所提供的同步类似于互斥对象,然而临界区对象只能被一个进程的线程所用。事件、互斥和旗语(semaphore)对象也能够应用在单进程应用程序中,但是临界区对象比互斥同步提供了更快、更高效的机制。同一个互斥对象一样,一个临界区对象一次只能被一个线程所占有,这种机制对于防止共享资源被同时存取是很有效的。至于哪个线程会拥有临界区,在顺序上没有保证,然而,系统对于每个线程都是公平的。

  #include
  #include
  #include
 
  CRITICAL_SECTION cs;
  int a[ 5 ];
 
  void Thread( void* pParams )
  {
    int i, num = 0;
 
    while ( TRUE )
    {
       EnterCriticalSection( &cs );
       for ( i = 0; i < 5; i++ ) a[ i ] = num;
       LeaveCriticalSection( &cs );
       num++;
    }
  }
 
  int main( void )

  {
    InitializeCriticalSection( &cs );
    _beginthread( Thread, 0, NULL );
 
    while( TRUE )
    {
       EnterCriticalSection( &cs );
       printf( "%d %d %d %d %d ",
               a[ 0 ], a[ 1 ], a[ 2 ],
               a[ 3 ], a[ 4 ] );
       LeaveCriticalSection( &cs );
    }
    return 0;
  }

互斥对象

一个互斥体是一个同步对象,其状态由信号态和非信号态两种:当没有任何线程占有该互斥体时,互斥体的状态被设置为信号态;当其为线程占有时则被设置为非信号态。而且一次只有一个线程能够占有这个互斥体,其名字正是源于各线程对共享资源的互斥存取。例如,为了禁止两个线程同时向共享区写数据,每一个线程都必须等待这个互斥体的拥有权,然后才能执行代码对内存进行存取。在对共享内存写完后,该线程释放互斥体。

两个或两个以上进行能够调用CreateMutex来产生同名的互斥体。事实上是第一个进程产生互斥体,接下来的进程只是打开现存互斥体的句柄。这使得多个进程获取同一个互斥体的句柄,并且也缓减了用户必须确保产生互斥体的进程必须先执行的压力。使用互斥对象技术,应该把bInitialOwner标志位设置为FALSE;否则,将会很难确定哪个进程有优先拥有权。

多进程能够拥有同一个互斥体的句柄,使进程内同步对象的使用成为可能。下面的共享对象机制是有效的:

  • 由CreateProcess函数所产生的子进程能够继承一个互斥体,如果CreateProcess的lpMutexAttributes 参数设置为允许继承的话。
  • 一个进程能够在DuplicateHandle 函数的调用中指定互斥体句柄来产生一个复制句柄,该句柄可以为另一个进程所用。
  • 一个进程能够在OpenMutex 和CreateMutex 函数的调用中指定互斥体的名字。

    一般说来,如果在同一个进程内部进行线程同步,临界区对象会更高效。

  #include
  #include
  #include
 
  HANDLE hMutex;
  int a[ 5 ];
 
  void Thread( void* pParams )
  {
     int i, num = 0;
 
     while ( TRUE )
     {
        WaitForSingleObject( hMutex, INFINITE );
        for ( i = 0; i < 5; i++ ) a[ i ] = num;
        ReleaseMutex( hMutex );
        num++;
     }
  }
 
  int main( void )
  {
     hMutex = CreateMutex( NULL, FALSE, NULL );
     _beginthread( Thread, 0, NULL );
 
     while( TRUE )

     {
        WaitForSingleObject( hMutex, INFINITE );
        printf( "%d %d %d %d %d ",
                a[ 0 ], a[ 1 ], a[ 2 ],
                a[ 3 ], a[ 4 ] );
        ReleaseMutex( hMutex );
     }
     return 0;
  }

事件对象

    如果我们想每一行的输出值是一个接一个增加的,也就是说在每次主线程打印完全局数组之后强迫次线程运行,该怎么办呢?

    事件对象也是一个同步对象,其状态可以由函数SetEvent 和PulseEvent 明确地设定为信号态。下面是事件对象的两种类型。

对象描述
人工复位事件一个事件对象会保持信号态直到由ResetEvent人工明确地复位为非信号态。 即使处于信号态下,任何数量的等待线程,或者在某一个等待函数中指定同样事件对象的线程,能够被释放。
自动复位事件一个事件对象的状态保持信号态,直到某一个等待线程被释放,此时系统会自动地将状态设定为非信号态。如果没有线程等待,事件对象的状态会保持信号态。

为了表明一个特定的事件已经发生,使用事件对象来发送一个信号给一个线程是很有用的。例如,在交迭输入与输出中,当交迭操作结束的时候,系统会把某个事件对象设定为信号态。单个线程能够指定不同的事件对象在几个同时交迭操作中,然后使用多个对象等待函数中的一个来等待任何一个事件对象被设置为信号态。

线程使用CreateEvent 函数来产生事件对象。该线程指定这个对象的初始状态,并且其为人工复位还是自动复位对象。该线程也能为事件对象指定一个名字。通过调用OpenEvent 函数,其他进程中的线程籍指定名字来打开一个现存事件对象的句柄。要想了解更多关于互斥体、事件、旗语和时钟对象的命名,请看进程内同步。

线程能使用PulseEvent 函数将事件对象的状态设为信号态,在释放掉若干等待线程后也能将其复位为非信号态。对于一个人工复位事件对象,所有的等待线程都被释放掉。对于一个自动复位事件对象,该函数仅仅释放掉一个等待线程,尽管很多线程在等待。如果没有线程等待,PulseEvent 只是把事件对象设为非信号态并返回。

  #include
  #include
  #include
 
  HANDLE hEvent1, hEvent2;
  int a[ 5 ];
 
  void Thread( void* pParams )
  {
     int i, num = 0;

     while ( TRUE )
     {
        WaitForSingleObject( hEvent2, INFINITE );
        for ( i = 0; i < 5; i++ ) a[ i ] = num;
        SetEvent( hEvent1 );
        num++;
     }
  }
 
  int main( void )
  {
     hEvent1 = CreateEvent( NULL, FALSE, TRUE, NULL );
     hEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL );
 
     _beginthread( Thread, 0, NULL );
 
     while( TRUE )
     {
        WaitForSingleObject( hEvent1, INFINITE );
        printf( "%d %d %d %d %d ",
                a[ 0 ], a[ 1 ], a[ 2 ],
                a[ 3 ], a[ 4 ] );
        SetEvent( hEvent2 );
     }
     return 0;
  }

  由Slimxp(何保全)翻译,转载请注明出处。

 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值