<!-- 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 函数 ExitThread
和 CreateThread
。如果有一个以上的线程在等待挂起的线程完成它对 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 是一个示例多线程程序,每次键入字母 a
或 A
时,它都创建新线程。每个线程都在屏幕的周围弹出不同颜色的笑脸。最多可以创建 32 个线程。键入 q
或 Q
时,发生正常的程序终止。
代码如下:
// 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 程序<!-- -->
<!-- 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
释放互斥体。
需
要小心管理的资源只有屏幕显示和静态数据两种。例如,程序可能有多个线程访问同一文件。由于其他线程可能已经移动了文件指针,因此每个线程在读取或写入之
前必须重新设置文件指针。另外,每个线程必须确保在它定位指针和访问文件两个时间之间没有被替换。这些线程应该通过用 WaitForSingleObject
和 ReleaseMutex
调用将每个文件的访问括起来,以使用信号灯协调对文件的访问。下面的代码示例演示此项技术:
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
-
在“文件”菜单上单击“新建”,然后单击“项目”。
-
在“项目类型”窗格中,单击“Win32”。
-
在“模板”窗格中,单击“Win32 控制台应用程序”,然后命名项目。
-
将包含 C 源代码的文件添加到项目中。
-
在“生成”菜单上,通过单击“生成”命令生成项目。
避免与多线程程序有关的问题
<!-- -->
<!-- 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
本书通过众多实例介绍了如何实现Windows下的多线程编程,既重点介绍了Win32API下的多线程编程和MFC下的多线程编程,又介绍了多线程机制在网络编程、图形编程和数据库中的应用。本书每一章都从简单的多线程实例出发...
在《Win32多线程程序设计》这本书中,Jim Beveridge和Robert Wiener告诉你什么时机、什么地点、什么方法可以使用多线程。本书主题包括: ● Internet开发范例,包括ISAPI和WinSock。 ● 如何在服务器中使用线程和...
基于jeremie_SYSU的基础上部署心得,由于是2019,同其不一样,配置方法略有差异,特整理记录,适用VS2019免费版。
一个非常简洁的多线程编程例子,纯C语言编写,非常适合多线程的入门
基于win32控制台的动态库Socket编程实例,在VC6.0编译环境下通过调试,可以看成是简单的实例
它有两种形式:WinInet API 包含一个 C 语言的函数集(Win32 Internet functions), MFC WinInet 类层次则是对前者的面向对象的封装。 程序能实现基本的FTP客户端功能,能登陆FTP服务器,显示登录客户目录下的文件和...
我们进行多线程编程,可以有多种选择,可以使用WindowsAPI,如果你在使用GTK,也可以使用GTK实现了的线程库,如果你想让你的程序有更多的移植性你好是选择POSIX中的Pthread函数库,我的程序是在Linux下写的,所以我...
本程序是用VC6.0写的一个C++多线程文件搜索程序,根据所需要搜索的文件后缀名与...(输入路径时注意是用单\ ,根目录表示如下 C:\, 文件后缀名为点开头例如: .txt) 希望对正在学习文件搜索,多线程编程的你们有帮助~
本文给大家分享的是C++多线程编程简单实例,由于C++本身没有多线程机制,在windows下我们使用调用SDK win32 api来实现,示例都很简单,讲解的也很详细,推荐给大家。
一、多线程编程基础 二、用win32 API的多线程编程 三、用MFC的多线程编程 四、线程同步 五、用C线程库编写线程
虽然是写这win32书籍,不过没想到,还有一本C++的书压缩进来了 Win32API教程第二版;Win32多线程程序设计;高质量C++C编程指南
这两个函数的详细情况请看Win32 API联机文档。 4、在同步代码结束后,使用ReleaseMutex(THandle)函数来标志。该函数只是释放互斥对象和线程的拥有者关系,并不释放互斥对象的句柄。 5、调用CloseHandle(THandle...
http://sourceware.org/pthreads-win32/ ,解压到一个目录。 2.找到include和lib文件夹,下面分别把它们添加到VC++6.0的头文件路径和静态链接库路径下面: a).Tools->Options,选择Directory页面,然后在Show ...
多线程CSerialPort类串口编程 MSCOMM控件串口编程 Win32API串口编程
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 ...
第一课 Windows编程和面向对象技术 1.1 Windows发展历史 ...第十二章 多线程与串行通信 12.1 多任务、进程和线程 12.2 线程的同步 12.3 串行通信与重叠I/O 12.4 一个通信演示程序 12.5 小结
第一课 Windows编程和面向对象技术 1.1 Windows发展历史 ...第十二章 多线程与串行通信 12.1 多任务、进程和线程 12.2 线程的同步 12.3 串行通信与重叠I/O 12.4 一个通信演示程序 12.5 小结
第2章 多线程串口编程工具CSerialPort类 16 2.1 CSerialPort类的功能及成员函数介绍 16 2.2 应用CSerialPort类编制基于对话框的应用程序 30 2.3 应用CSerialPort类编制基于单文档的应用程序 35 2.4 对CSerialPort...
8.4.6 示例——图像浏览器 第9章 多线程编程 9.6.1 示例——使用全局变量通信 9.6.2 示例——使用Windows消息通信 9.7.5 示例——使用CriticalSection对象 9.7.7 示例——使用Mutex对象 9.7.9...