MSDN:VC多线程程序

41 篇文章 0 订阅
40 篇文章 0 订阅

若要使用第一行运行时库 请忽略第2行的这些库

单线程 (libc.lib,libc = library of C )

libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib

多线程 (libcmt.lib , libcmt = library of c multithreading )

libc.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib

用 DLL 的多线程 (msvcrt.lib , msvcrt = microsoft visual c runtime )

libc.lib、libcmt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib

调试单线程 (libcd.lib,libcd = library of c debug)

libc.lib、libcmt.lib、msvcrt.lib、libcmtd.lib、msvcrtd.lib

调试多线程 (libcmtd.lib , libcmtd = libcmt debug )

libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、msvcrtd.lib

使用 DLL 的调试多线程 (msvcrtd.lib , msvcrtd = msvcrt debug )

libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib
零、多线程
线程实质上是程序中的执行路径。也是 Win32 安排的最小执行单元。线程包括堆栈、CPU 寄存器的状态和系统计划程序执行列表中的项。每个线程共享所有进程的资源。

进程包括一个或多个线程和代码、数据和内存中的其他程序资源。典型的程序资源是打开的文件、信号灯和动态分配的内存。当系统计划程序给予其中的一个线程执行控制时,即执行程序。计划程序确定应当运行哪些线程以及它们应当何时运行。较低优先级的线程可能必须等到较高优先级的线程完成任务后才能运行。在多处理器计算机上,计划程序可以将单个线程移到不同的处理器以平衡 CPU 负荷。

进程中的每个线程都独立运行。除非使这些线程相互可见,否则线程分别执行,对进程中的其他线程一无所知。线程共享公共资源,但是,必须使用信号灯或其他进程间的通信方法协调它们的工作

一、C 和 Win32 进行多线程编程
Microsoft Visual C++ provides support for creating multithread applications with Microsoft Windows: Windows XP, Windows 2000, Windows NT, Windows Me, and Windows 98.You should consider using more than one thread if your application needs to manage multiple activities, such as simultaneous keyboard and mouse input.

使用 Visual C++ 的多线程编程有两种方式:使用 Microsoft 基础类库 (MFC),或使用 C 运行库和 Win32 API。有关使用 MFC 创建多线程应用程序的信息,请先阅读以下使用 C 进行多线程处理的主题

二、线程控制的 C 运行库函数

所有 Win32 程序都至少有一个线程。任何线程都可以创建附加线程。线程可以快速完成其工作,然后终止;也可以在程序的生存期内保持活动状态。

LIBCMT 和 MSVCRT C 运行库提供以下用于创建和终止线程的函数: _beginthread, _beginthreadex 和 _endthread, _endthreadex。

_beginthread 和 _beginthreadex 函数创建新线程;如果操作成功,则返回线程标识符。 线程完成执行时自动终止,或者可通过调用 _endthread 或 _endthreadex 自行终止。


说明
如果要从使用 Libcmt.lib 生成的程序调用 C 运行时例程,则必须使用 _beginthread 或 _beginthreadex 函数启动线程。 不要使用 Win32 函数 ExitThread 和 CreateThread。 如果有一个以上的线程在等待挂起的线程完成它对 C 运行时数据结构的访问时被阻塞,使用 SuspendThread 会导致死锁。



_beginthread 和 _beginthreadex 函数
_beginthread 和 _beginthreadex 函数用来创建新线程。 线程与进程中的其他线程共享进程的代码和数据段,但是线程具有自己的唯一寄存器值、堆栈空间和当前指令地址。系统给予每个线程 CPU 时间,使进程中的所有线程都可以同时执行。

_beginthread 和 _beginthreadex 与 Win32 API 中的 CreateThread 函数类似,但有如下差异:

它们初始化某些 C 运行库变量。只有在线程中使用 C 运行库时,这一点才很重要。

CreateThread 帮助提供对安全属性的控制。 可以使用此函数启动处于挂起状态的线程。

如果成功的话, _beginthread 和 _beginthreadex 返回新线程的句柄;如果有错误的话,则返回错误代码。


_endthread 和 _endthreadex 函数
_endthread 函数终止由 _beginthread 创建的线程(同样, _endthreadex 终止由 _beginthreadex 创建的线程)。 Threads terminate automatically when they finish._endthread and _endthreadex are useful for conditional termination from within a thread. 例如,专门用于通信处理的线程若无法获取对通信端口的控制,则会退出。

// sample_multithread_c_program.c
// compile with: /c
//
//  Bounce - Creates a new thread each time the letter 'a'
// is typed.  
//  Each thread bounces a happy face of a different color
// around
//  the screen.  All threads are terminated when the letter 
//'Q' is
//  entered.  
//

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <process.h>

#define MAX_THREADS  32

// The function getrandom returns a random number between 
// min and max, which must be in integer range.  #define getrandom( min, max ) (SHORT)((rand() % (int)(((max) + 1) - \
                               (min))) + (min))

int main( void );                    // Thread 1: main 
void KbdFunc( void  );               // Keyboard input, thread dispatch
void BounceProc( void * MyID );      // Threads 2 to n: display 
void ClearScreen( void );            // Screen clear 
void ShutDown( void );               // Program shutdown 
void WriteTitle( int ThreadNum );    // Display title bar information 

HANDLE  hConsoleOut;                 // Handle to the console 
HANDLE  hRunMutex;                   // "Keep Running" mutex 
HANDLE  hScreenMutex;                // "Screen update" mutex
int     ThreadNr;                    // Number of threads started 
CONSOLE_SCREEN_BUFFER_INFO csbiInfo; // Console information 


int main() // Thread One 
{
    // Get display screen information & clear the screen.  hConsoleOut = GetStdHandle( STD_OUTPUT_HANDLE );
    GetConsoleScreenBufferInfo( hConsoleOut, &csbiInfo );
    ClearScreen();
    WriteTitle( 0 );

    // Create the mutexes and reset thread count.  hScreenMutex = CreateMutex( NULL, FALSE, NULL );  // Cleared 
    hRunMutex = CreateMutex( NULL, TRUE, NULL );      // Set 
    ThreadNr = 0;

    // Start waiting for keyboard input to dispatch threads or exit.  KbdFunc();

    // All threads done.  Clean up handles.  CloseHandle( hScreenMutex );
    CloseHandle( hRunMutex );
    CloseHandle( hConsoleOut );
}

void ShutDown( void ) // Shut down threads 
{
    while ( ThreadNr > 0 )
    {
        // Tell thread to die and record its death.  ReleaseMutex( hRunMutex );
        ThreadNr--;   
    }

    // Clean up display when done
    WaitForSingleObject( hScreenMutex, INFINITE );
    ClearScreen();
}

void KbdFunc( void ) // Dispatch and count threads.  {
    int         KeyInfo;

    do
    {
        KeyInfo = _getch();
        if ( tolower( KeyInfo ) == 'a' && 
             ThreadNr < MAX_THREADS )
        {
            ThreadNr++;
            _beginthread( BounceProc, 0, &ThreadNr );
            WriteTitle( ThreadNr );
        }
    } while( tolower( KeyInfo ) != 'q' );

    ShutDown();
}

void BounceProc( void *pMyID )
{
    char    MyCell, OldCell;
    WORD    MyAttrib, OldAttrib;
    char    BlankCell = 0x20;
    COORD   Coords, Delta;
    COORD   Old = {0,0};
    DWORD   Dummy;
    char    *MyID = (char*)pMyID;

    // Generate update increments and initial 
    // display coordinates.  srand( (unsigned int) *MyID * 3 );

    Coords.X = getrandom( 0, csbiInfo.dwSize.X - 1 );
    Coords.Y = getrandom( 0, csbiInfo.dwSize.Y - 1 );
    Delta.X = getrandom( -3, 3 );
    Delta.Y = getrandom( -3, 3 );

    // Set up "happy face" & generate color 
    // attribute from thread number.  if( *MyID > 16)
        MyCell = 0x01;          // outline face 
    else
        MyCell = 0x02;          // solid face 
    MyAttrib =  *MyID & 0x0F;   // force black background 

    do
    {
        // Wait for display to be available, then lock it.  WaitForSingleObject( hScreenMutex, INFINITE );

        // If we still occupy the old screen position, blank it out.  ReadConsoleOutputCharacter( hConsoleOut, &OldCell, 1, 
                                    Old, &Dummy );
        ReadConsoleOutputAttribute( hConsoleOut, &OldAttrib, 1, 
                                    Old, &Dummy );
        if (( OldCell == MyCell ) && (OldAttrib == MyAttrib))
            WriteConsoleOutputCharacter( hConsoleOut, &BlankCell, 1,
                                         Old, &Dummy );

        // Draw new face, then clear screen lock 
        WriteConsoleOutputCharacter( hConsoleOut, &MyCell, 1, 
                                     Coords, &Dummy );
        WriteConsoleOutputAttribute( hConsoleOut, &MyAttrib, 1, 
                                     Coords, &Dummy );
        ReleaseMutex( hScreenMutex );

        // Increment the coordinates for next placement of the block.  Old.X = Coords.X;
        Old.Y = Coords.Y;
        Coords.X += Delta.X;
        Coords.Y += Delta.Y;

        // If we are about to go off the screen, reverse direction 
        if( Coords.X < 0 || Coords.X >= csbiInfo.dwSize.X )
        {
            Delta.X = -Delta.X;
            Beep( 400, 50 );
        }
        if( Coords.Y < 0 || Coords.Y > csbiInfo.dwSize.Y )
        {
            Delta.Y = -Delta.Y;
            Beep( 600, 50 );
        }
    }
    // Repeat while RunMutex is still taken.  while ( WaitForSingleObject( hRunMutex, 75L ) == WAIT_TIMEOUT );
}

void WriteTitle( int ThreadNum )
{
    enum { 
        sizeOfNThreadMsg = 80 
    };
    char    NThreadMsg[sizeOfNThreadMsg];

    sprintf_s( NThreadMsg, sizeOfNThreadMsg, 
               "Threads running: %02d.  Press 'A' "
               "to start a thread,'Q' to quit.", ThreadNum );
    SetConsoleTitle( NThreadMsg );
}

void ClearScreen( void )
{
    DWORD    dummy;
    COORD    Home = { 0, 0 };
    FillConsoleOutputCharacter( hConsoleOut, ' ', 
                                csbiInfo.dwSize.X * csbiInfo.dwSize.Y, 
                                Home, &dummy );
}

三、编写多线程 Win32 程序
编写具有多线程的程序时,必须协调这些线程的行为和 程序资源的用法。 还必须确定每个线程接收 自己的堆栈。


说明
有关从 MFC 角度进行的类似讨论,请参见 多线程处理:编程提示和 多线程处理:何时使用同步类。


线程间共享公共资源说明

每个线程具有自己的堆栈和自己的 CPU 寄存器副本。其他资源(如文件、静态数据和堆内存)由进程中的所有线程共享。使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。

当多线程访问静态数据时,程序必须提供可能的资源冲突。假定有这样一个程序,一个线程更新静态数据结构,该结构包含要由其他线程显示的项的 x, y 坐标。 如果更新线程更改了 x 坐标并且在更改 y 坐标之前被取代,则可能会在更新 y 坐标之前安排显示线程。 该项可能会在错误的位置显示。通过使用信号灯控制对结构的访问,可以避免此问题。

互斥体( mutual exclusion 的缩写)是异步执行的线程或进程间通信的方式。 此通信通常用于协调多个线程或进程的活动,通常通过锁定和取消锁定资源控制对共享资源的访问。为解决此 x, y 坐标的更新问题,更新线程将设置 mutex,在执行更新之前指示数据结构正在使用。 更新线程将在两个坐标全部处理完之后清除互斥体。显示线程在更新显示之前必须等待清除互斥体。由于进程被阻止且直到清除 mutex 后才能继续,因此等待 mutex 的进程通常称为在 mutex 上“阻止”。

上面的多线程 C 程序使用名为 ScreenMutex 的 mutex 协调屏幕更新。 每当其中的一个显示线程准备写入屏幕时,将调用 WaitForSingleObject,使用 ScreenMutex 的句柄和常数 INFINITE 指示 WaitForSingleObject 调用应该在互斥体上阻止但不应该超时。 如果清除了 ScreenMutex,等待函数将设置互斥体,使其他线程不能影响显示并继续执行该线程。 否则,线程将阻止,直到清除互斥体。线程完成显示更新后,通过调用 ReleaseMutex 释放互斥体。

需要小心管理的资源只有屏幕显示和静态数据两种。例如,程序可能有多个线程访问同一文件。由于其他线程可能已经移动了文件指针,因此每个线程在读取或写入之前必须重新设置文件指针。另外,每个线程必须确保在它定位指针和访问文件两个时间之间没有被替换。这些线程应该通过用 WaitForSingleObject 和 ReleaseMutex 调用将每个文件的访问括起来,以使用信号灯协调对文件的访问。 下面的代码示例演示此项技术:

HANDLE    hIOMutex= CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

HANDLE    hIOMutex= CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

线程堆栈

All of an application’s default stack space is allocated to the first thread of execution, which is known as thread 1.As a result, you must specify how much memory to allocate for a separate stack for each additional thread your program needs.如果必要,操作系统将为线程分配附加的堆栈空间,但必须指定默认值。

_beginthread 调用中的第一个参数是指向将要执行线程的 BounceProc 函数的指针。 第二个参数指定线程的默认堆栈大小。最后一个参数是传递到 BounceProc 的 ID 号。 BounceProc 用 ID 号为随机数生成器提供种子,并选择线程的颜色属性和显示字符。

调用 C 运行库或 Win32 API 的线程必须要有足够的堆栈空间用于所调用的库和 API 函数。C printf 函数需要 500 多个字节的堆栈空间,调用 Win32 API 例程时应该有 2K 的可用堆栈空间。

因为每个线程都自己的堆栈,因此可以通过尽可能少地使用静态数据而避免潜在的数据项冲突。对于线程可私有的所有数据,将程序设计为使用自动堆栈变量。Bounce.c 程序中仅有的全局变量是 mutex 或初始化之后不再更改的变量。

Win32 还提供“线程本地存储”(TLS),存储基于线程的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值