`
hereson3
  • 浏览: 160189 次
  • 性别: Icon_minigender_2
  • 来自: 广州
社区版块
存档分类
最新评论

使用 C 和 Win32 进行多线程编程

阅读更多
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

MicrosoftVisual C++ 支持在 MicrosoftWindows(Windows XP、Windows 2000、Windows NT、Windows Me 和Windows 98)下创建多线程应用程序。如果您的应用程序需要管理多个活动(如同时进行键盘和鼠标输入),则您应当考虑使用多线程。一个线程可以 处理键盘输入,而另一个线程可以筛选鼠标活动。第三个线程可以根据鼠标和键盘线程的数据更新显示屏幕。同时,其他线程可以访问磁盘文件或从通信端口获取数 据。

使用 Visual C++ 的多线程编程有两种方式:使用 Microsoft 基础类库 (MFC),或使用 C 运行时库和 Win32 API。

多线程程序<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

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

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

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

多线程编程的库支持<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

现在,所有版本的 CRT 都支持多线程处理,但非锁定版本的某些函数除外。

多线程编程的包含文件<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

在 库中实现 C 运行时库函数时,标准包含文件声明它们。如果使用完全优化 (/Ox) 或 fastcall 调用约定 (/Gr) 选项,编译器假设应使用寄存器调用约定调用所有函数。运行时库函数是使用 C 调用约定编译的,标准包含文件中的声明通知编译器生成对这些函数的正确外部引用。

线程控制的 C 运行时库函数<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

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

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

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

注意

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

<!-- -->

_beginthread 和 _beginthreadex 函数

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

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

  • 1._beginthread_beginthreadex 使您可以将多个参数传递到线程。

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

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

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

<!-- -->

_endthread 和 _endthreadex 函数

_endthread 函数终止由 _beginthread 创建的线程(同样,_endthreadex 终止由 _beginthreadex 创建的线程)。线程会在完成时自动终止。_endthread_endthreadex 用于从线程内部进行条件终止。例如,专门用于通信处理的线程若无法获取对通信端口的控制,则会退出。

多线程 C 程序示例<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

Bounce.c 是一个示例多线程程序,每次键入字母 aA 时,它都创建新线程。每个线程都在屏幕的周围弹出不同颜色的笑脸。最多可以创建 32 个线程。键入 qQ 时,发生正常的程序终止。

代码如下:

// 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 );
}

输入

a
q

 

编写多线程 Win32 程序<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

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

<!-- -->

线程间共享公共资源

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

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

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

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

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

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

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

<!-- -->

线程堆栈

应用程序的所有默认堆栈空间都被分配到称为线程 1 的第一个执行线程。因此,必须为程序所需的每个附加线程的单独堆栈指定分配的内存量。如果必要,操作系统将为线程分配附加的堆栈空间,但必须指定默认值。

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

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

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

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

编译和链接多线程程序<!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

多线程 C 程序示例 中对 Bounce.c 程序进行了介绍。

默认情况下,程序以多线程形式进行编译。

从开发环境内编译和链接多线程程序 Bounce.c

  1. 在“文件”菜单上单击“新建”,然后单击“项目”。

  2. 在“项目类型”窗格中,单击“Win32”。

  3. 在“模板”窗格中,单击“Win32 控制台应用程序”,然后命名项目。

  4. 将包含 C 源代码的文件添加到项目中。

  5. 在“生成”菜单上,通过单击“生成”命令生成项目。

 

避免与多线程程序有关的问题 <!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

在创建、链接或执行多线程 C 程序时可能会遇到几种问题。下表将对一些更常见的问题进行描述。

问题 可能的原因

出现一个消息框,显示程序已导致保护冲突。

许多 Win32 编程错误导致保护冲突。导致保护冲突的常见原因是间接将数据分配给空指针。因为这会导致程序试图访问不属于它的内存,所以发出保护冲突。

检测导致保护冲突原因的一个很容易的方式就是使用调试信息编译程序,然后在 Visual C++ 环境中通过调试器运行程序。发生保护错误时,Windows 将控制传输到调试器,光标会定位在导致问题的行上。

程序生成许多编译和链接错误。

通过将编译器的警告等级设置为最高值之一并注意警告消息,可以消除许多潜在问题。通过使用等级为 3 或等级为 4 的警告等级选项,可以检测意外的数据转换、丢失的函数原型和非 ANSI 功能的使用。

线程本地存储 (TLS) <!-- -->
<!-- Content type: DocStudio. Transform: devdiv2mtps.xslt.-->

 

线 程本地存储 (TLS) 是一个方法,通过该方法,给定的多线程进程中的每个线程都可以分配存储线程特定数据的位置。动态绑定(运行时)线程特定数据是通过 TLS API(TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree)的方式支持的。除了现有的 API 实现,Win32 和 Visual C++ 编译器现在还支持静态绑定(加载时间)基于线程的数据。

<!-- -->

TLS 的 API 实现

线程本地存储是通过 Win32 API 层和编译器实现的。

Visual C++ 编译器提供了一个关键字(而不通过 API 层)使 TLS 操作更加自动化。将在下一节(TLS 的编译器实现)描述此语法。

<!-- -->

TLS 的编译器实现

为了支持 TLS,已将新属性 thread 添加到了 C 和 C++ 语言,并由 Visual C++ 编译器支持。此属性是一个扩展存储类修饰符,如上一节中所述。使用 __declspec 关键字声明 thread 变量。例如,以下代码声明了一个整数线程局部变量,并用一个值对其进行初始化:

__declspec( thread ) int tls_i = 1;
分享到:
评论

相关推荐

    c_win32多线程编程[归类].pdf

    c_win32多线程编程[归类].pdf

    Windows多线程编程技术与实例.pdf

     本书通过众多实例介绍了如何实现Windows下的多线程编程,既重点介绍了Win32API下的多线程编程和MFC下的多线程编程,又介绍了多线程机制在网络编程、图形编程和数据库中的应用。本书每一章都从简单的多线程实例出发...

    windows32多线程编程

    在《Win32多线程程序设计》这本书中,Jim Beveridge和Robert Wiener告诉你什么时机、什么地点、什么方法可以使用多线程。本书主题包括: ● Internet开发范例,包括ISAPI和WinSock。 ● 如何在服务器中使用线程和...

    WIN10_VS2019_配置_多线程_C语言.docx

    基于jeremie_SYSU的基础上部署心得,由于是2019,同其不一样,配置方法略有差异,特整理记录,适用VS2019免费版。

    简单的多线程编程例子

    一个非常简洁的多线程编程例子,纯C语言编写,非常适合多线程的入门

    基于win32控制台的动态库Socket编程实例,

    基于win32控制台的动态库Socket编程实例,在VC6.0编译环境下通过调试,可以看成是简单的实例

    实验7-WinInet 与多线程编程

    它有两种形式:WinInet API 包含一个 C 语言的函数集(Win32 Internet functions), MFC WinInet 类层次则是对前者的面向对象的封装。 程序能实现基本的FTP客户端功能,能登陆FTP服务器,显示登录客户目录下的文件和...

    C语言使用pthread多线程编程

    我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你好是选择POSIX中的Pthread函数库,我的程序是在Linux下写的,所以我...

    WIN32控制台 C++多线程文件搜索程序

    本程序是用VC6.0写的一个C++多线程文件搜索程序,根据所需要搜索的文件后缀名与...(输入路径时注意是用单\ ,根目录表示如下 C:\, 文件后缀名为点开头例如: .txt) 希望对正在学习文件搜索,多线程编程的你们有帮助~

    C++多线程编程简单实例

    本文给大家分享的是C++多线程编程简单实例,由于C++本身没有多线程机制,在windows下我们使用调用SDK win32 api来实现,示例都很简单,讲解的也很详细,推荐给大家。

    13 windows多线程.ppt

    一、多线程编程基础 二、用win32 API的多线程编程 三、用MFC的多线程编程 四、线程同步 五、用C线程库编写线程

    Win32_Book

    虽然是写这win32书籍,不过没想到,还有一本C++的书压缩进来了 Win32API教程第二版;Win32多线程程序设计;高质量C++C编程指南

    Delphi多线程编程之三 同步读写全局数据

    这两个函数的详细情况请看Win32 API联机文档。 4、在同步代码结束后,使用ReleaseMutex(THandle)函数来标志。该函数只是释放互斥对象和线程的拥有者关系,并不释放互斥对象的句柄。 5、调用CloseHandle(THandle...

    C语言编程中借助pthreads库进行多线程编程的示例

     http://sourceware.org/pthreads-win32/ ,解压到一个目录。 2.找到include和lib文件夹,下面分别把它们添加到VC++6.0的头文件路径和静态链接库路径下面:  a).Tools-&gt;Options,选择Directory页面,然后在Show ...

    Visual C++_Turbo C串口通信编程实践

    多线程CSerialPort类串口编程 MSCOMM控件串口编程 Win32API串口编程

    网络编程实用教程(第三版).zip

    7.1 WinSock为什么需要多线程编程 188 7.1.1 WinSock的两种I/O模式 188 7.1.2 两种模式的优缺点及解决方法 189 7.2 Win32操作系统下的多进程多线程机制 189 7.2.1 Win32 OS是单用户多任务的操作系统 189 ...

    VC++可视化编程指南

    第一课 Windows编程和面向对象技术  1.1 Windows发展历史 ...第十二章 多线程与串行通信  12.1 多任务、进程和线程  12.2 线程的同步  12.3 串行通信与重叠I/O  12.4 一个通信演示程序  12.5 小结

    VC++ 编程指南_中文chm

    第一课 Windows编程和面向对象技术  1.1 Windows发展历史 ...第十二章 多线程与串行通信  12.1 多任务、进程和线程  12.2 线程的同步  12.3 串行通信与重叠I/O  12.4 一个通信演示程序  12.5 小结

    Visual C++_Turbo C 串口通信编程实践.(电子工业.龚建伟.熊光明) 第二版 电子版

    第2章 多线程串口编程工具CSerialPort类 16 2.1 CSerialPort类的功能及成员函数介绍 16 2.2 应用CSerialPort类编制基于对话框的应用程序 30 2.3 应用CSerialPort类编制基于单文档的应用程序 35 2.4 对CSerialPort...

    Visual C++.NET编程技术体验

    8.4.6 示例——图像浏览器 第9章 多线程编程 9.6.1 示例——使用全局变量通信 9.6.2 示例——使用Windows消息通信 9.7.5 示例——使用CriticalSection对象 9.7.7 示例——使用Mutex对象 9.7.9...

Global site tag (gtag.js) - Google Analytics