<!-- 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;
分享到:
相关推荐
**标题解析:** "WIN32多线程编程" 是一个关于在Windows操作系统环境下使用Win32 API进行多线程程序设计的主题。多线程编程允许应用程序同时执行多个任务,提高资源利用率,优化性能,特别是在现代多核处理器系统中...
在多线程编程中,MFC提供了一系列的同步对象,如CMutex、CSemaphore等,以便于实现线程间的同步和通信。这些类是直接基于Win32 API进行封装的,它们使程序员能够更加方便地构建复杂的多线程应用程序。 总结来说,...
标题《c_win32多线程编程[归类].pdf》指出了本文档的主题是关于在Windows平台上使用C语言进行多线程编程的相关知识。在Windows平台上,多线程编程通常是通过Win32 API来实现的。Win32 API提供了丰富的函数和数据结构...
在Windows 10环境下使用Visual Studio 2019(VS2019)进行C语言的多线程编程,需要进行一系列配置步骤。这里主要介绍如何配置VS2019以便支持C语言的多线程功能,以及解决在配置过程中可能遇到的问题。 首先,我们...
线程间通信和同步是多线程编程中的关键问题。Win32 API提供了多种机制,如事件对象(`CreateEvent`)、信号量(`CreateSemaphore`)、互斥量(`CreateMutex`)和临界区(`EnterCriticalSection`/`...
总的来说,通过这个压缩包,你可以了解到C语言在Windows环境下实现多线程编程的方法,以及如何使用Mutex、Semaphore和Event来实现线程间的同步和通信。这对于提升你的Windows系统级编程能力,尤其是理解和处理并发...
### VisualC++多线程编程详解 ...通过Win32 API提供的工具和函数,可以在Visual C++环境下有效地管理和控制线程,解决多线程编程中的常见问题。然而,良好的设计和严谨的测试仍然是确保多线程程序质量的关键。
在Windows平台上进行多文件多线程编程是一项复杂但至关重要的任务,这涉及到多个技术领域,如图形用户界面(GUI)编程、线程管理和文件操作。本项目“window多文件多线程编程”显然旨在深入实践这些技能,通过实际...
由于这是一个简单的实例,可能不涉及多线程、并发连接或高级错误处理,但它是学习Socket编程的基础。 通过分析压缩包中的"socket"文件,可能包含了源代码文件、编译配置和可能的可执行文件。学习这个实例,开发者...
标题中的“VC写的小巧多线程下载工具30K哦,win32wget”指的是一个使用Microsoft Visual C++(简称VC)编写的轻量级下载程序,该程序只有大约30KB的大小,实现了多线程下载功能,并且其灵感或设计可能来源于Linux下...
在《Win32多线程程序设计》这本书中,Jim Beveridge和Robert Wiener告诉你什么时机、什么地点、什么方法可以使用多线程。本书主题包括: ● Internet开发范例,包括ISAPI和WinSock。 ● 如何在服务器中使用线程和...
在Windows平台上进行多线程编程是一项复杂而重要的技术,它允许程序在同一进程中同时执行多个并发的任务,从而提高系统的效率和响应性。Windows操作系统是多任务的,这意味着在一个进程中可以有多个线程,它们共享...
理解并熟练掌握`Win32`多线程编程,对于开发高效、可靠的Windows应用程序至关重要。 总之,`Win32`多线程程序中的`EXITCODE`是一个重要的通信机制,它提供了关于程序或线程执行结果的信息。通过合理地创建、管理和...
pthreads是遵循IEEE POSIX 1003.1c-1995标准的线程API,被广泛应用于多种操作系统,如Linux、Solaris等,但不包括Win32和OS/2。 **pthreads主要函数**: 1. **线程创建**:`pthread_create()`用于创建新线程,指定...