一 线程局部存储 Thread Local Storage
1 由于多个线程使用同一个变量,各个线程
都对变量进行操作,那么变量的值会被不同
线程操作覆盖。
通常 变量A <-- 线程A
<-- 线程B
TLS 变量A <-- 线程A
变量A <-- 线程B
2 TLS的使用
2.1 使用关键字 __declspec(thread)
__declspec(thread) CHAR * g_pszText2 = NULL;
2.2 TLS相关API
2.2.1 创建TLS索引
DWORD TlsAlloc(VOID)
返回一个TLS索引号
2.2.2 设置值
BOOL TlsSetValue( DWORD dwTlsIndex, //TLS索引 LPVOID lpTlsValue //保存的值 );
2.2.3 获取值
LPVOID TlsGetValue( DWORD dwTlsIndex //TLS索引 );
返回存放在索引内的值
2.2.4 释放
BOOL TlsFree( DWORD dwTlsIndex //TLS索引 );
/*File : ThreadTls.cpp
*Auth : sjin
*Date : 20140726
*Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "stdlib.h"
#include "windows.h"
CHAR * g_pszText = NULL;
DWORD g_nTlsIndex = 0;
void Print(CHAR * pszText )
{
printf("\n===== %s ======\n",pszText);
printf( "g_pszText: %s\n", g_pszText );
//从TLS索引中获取值
CHAR * pszText1 = (CHAR *)TlsGetValue( g_nTlsIndex );
strcpy( g_pszText, pszText1 );
printf( "TLS: %s\n", pszText1 );
printf("=================\n\n");
}
DWORD WINAPI PrintProc( LPVOID pParam )
{
CHAR * pszText = (CHAR *)pParam;
g_pszText = (CHAR *)malloc( 100 );
strcpy( g_pszText, pszText );
//将值保存到TLS索引当中
TlsSetValue( g_nTlsIndex, g_pszText );
while( 1 )
{
Print(pszText );
Sleep( 1000 );
}
return 0;
}
void Create( )
{
HANDLE hThread_1 = NULL;
DWORD nThreadID_1 = 0;
HANDLE hThread_2 = NULL;
DWORD nThreadID_2 = 0;
CHAR szText1[] = "ThreadProc 1----------";
hThread_1 = CreateThread( NULL, 0,PrintProc, szText1, 0, &nThreadID_1 );
Sleep(1000); // 让线程1 2 打印错开。
CHAR szText2[] = "-----ThreadProc 2-----";
hThread_2 = CreateThread( NULL, 0,PrintProc, szText2, 0, &nThreadID_2 );
Sleep(5000);
//退出线程
TerminateThread(hThread_1,0);
TerminateThread(hThread_2,0);
WaitForSingleObject( hThread_1, INFINITE );
printf("Create exit thread_id : %d \n",nThreadID_1);
WaitForSingleObject( hThread_2, INFINITE );
printf("Create exit thread_id : %d \n",nThreadID_2);
}
int _tmain(int argc, _TCHAR* argv[])
{
//创建TLS索引号
g_nTlsIndex = TlsAlloc( );
//创建线程
Create( );
//释放索引
TlsFree( g_nTlsIndex );
return 0;
}
二 线程同步
1 多线程的问题
A停止 -> B开始 -〉B停止 -> A开始
当线程停止会保存寄存器的状态。
当线程开始会恢复寄存器的状态。
AB线程都使用printf的问题:
A线程调用printf时,printf正在输出
当中,A挂起,B执行,B线程也调用printf输出B的数据,画面会出现A的数据输出1部分,然后是B的数据;
B挂起,A执行, A继续输出自己的数据.
所以,由于多线程的切换,产生数据混乱.
2 问题的解决 - 同步机制
2.1 原子锁
2.2 临界区
2.3 事件
2.4 互斥
2.5 信号量
2.6 可等候定时器
3 等候多个内核对象事件
DWORD WaitForMultipleObjects( DWORD nCount,//句柄的数量 CONST HANDLE *lpHandles,//句柄数组 BOOL fWaitAll, //等候方式 DWORD dwMilliseconds );//等候时间
等候方式fWaitAll:
TRUE - 每个句柄都有事件,解除阻塞
FALSE - 其中一个句柄有事件,解除阻塞
三 原子锁
1 g_nValue++执行
线程A通过寄存器完成加法运算,假设g_nValue正在加到10000时,线程切换到B,A的寄存器中保存10000数字,B从10000开始加数据,当B加到15000时,线程切换到A,A恢复寄存器的值,A会继续从10000开始累加,就将B完成5000的加法覆盖.
2 原子锁
执行单个指令时,锁定操作,不允许其他线程访问.
3 用法
InterlockedIncrement ++运算
InterlockedDecrement --运算
InterlockedCompareExchange ?运算
/* File :InterLock.cpp
* Auth : sjin
* Date : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "windows.h"
LONG g_nValue1 = 0;
LONG g_nValue2 = 0;
DWORD WINAPI InterProc1( LPVOID pParam )
{
for( int nIndex=0; nIndex<10000000; nIndex++ ){ //普通++
g_nValue1++;
}
return 0;
}
DWORD WINAPI InterProc2( LPVOID pParam )
{
for( int nIndex=0; nIndex<10000000; nIndex++ ){ //原子锁++(lock)
InterlockedIncrement( &g_nValue2 );
}
return 0;
}
void Create( )
{
DWORD nThreadID = 0;
HANDLE hThread[4] = { NULL };
hThread[0] = CreateThread( NULL, 0, InterProc1, NULL, 0, &nThreadID );
hThread[1] = CreateThread( NULL, 0, InterProc1, NULL, 0, &nThreadID );
hThread[2] = CreateThread( NULL, 0, InterProc2, NULL, 0, &nThreadID );
hThread[3] = CreateThread( NULL, 0, InterProc2, NULL, 0, &nThreadID );
WaitForMultipleObjects( 4, hThread, TRUE, INFINITE );
printf( "Value1=%d Value2=%d\n", g_nValue1, g_nValue2 );
}
int _tmain(int argc, _TCHAR* argv[])
{
Create( );
return 0;
}
四 临界区
1 临界区作用
线程在执行代码时,将代码锁定,不允许其他线程执行,只有该线程离开后,其他线程才能使用这些代码
2 临界区的使用
2.1 初始化临界区
VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection //临界区结构地址 );
2.2 临界区加锁
VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection // pointer to critical //临界区 );
2.3 临界区解锁
VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection // 临界区 );
2.4 释放临界区
VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection //临界区 );
3 和原子锁相比
原子锁是一条语句
临界区可以完成多条语句的锁定.
/* File :CriticalSection.cpp
* Auth : sjin
* Date : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "conio.h"
#include "windows.h"
CRITICAL_SECTION g_cs = { 0 };
LONG nValue = 0;
void Print( )
{ //进入临界区 - 加锁
EnterCriticalSection( &g_cs );
nValue++;
printf( "Long long long.......%d \n", nValue );
//离开临界区 - 解锁
LeaveCriticalSection( &g_cs );
}
DWORD WINAPI PrintProc( LPVOID pParam )
{
while( 1 ){
Print( );
Sleep( 100 );
}
return 0;
}
void Create( )
{
DWORD nThreadID = 0;
HANDLE hThread[2] = { 0 };
hThread[0] = CreateThread( NULL, 0, PrintProc, NULL, 0, &nThreadID );
hThread[1] = CreateThread( NULL, 0, PrintProc, NULL, 0, &nThreadID );
getch( );
TerminateThread(hThread[0],1);
TerminateThread(hThread[1],2);
//等待线程退出
WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
printf("thread exit....");
}
int main(int argc, char* argv[])
{ //初始化临界区
InitializeCriticalSection( &g_cs );
Create( );
//删除临界区
DeleteCriticalSection( &g_cs );
return 0;
}
五 事件
1 事件
通知的作用,当收到事件时,线程可以执行. 否则,线程将等候事件发生.
2 事件的用法
2.1 创建事件
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes,//安全属性 BOOL bManualReset,//重置方式 BOOL bInitialState, //初始化状态 LPCTSTR lpName //名称 );
返回创建好的事件句柄
bManualReset - 事件重置方式, TRUE手动和FALSE自动重置. 如果为FALSE,系统在等候到事件后,会自动将事件重置为无信号状态. 如果为TRUE,我们必须自己使用ResetEvent重置状态.
bInitialState - 初始化状态, TRUE为有信号,FALSE无信号.
2.2 等候事件
WaitForSingleObject/
WaitForMultipleObjects
2.3 触发事件
BOOL SetEvent( HANDLE hEvent //事件句柄 );
2.4 关闭事件
CloseHandle
2.5 重置事件
BOOL ResetEvent( HANDLE hEvent //事件句柄 );
2.6 其他函数
OpenEvent
PulseEvent
/* File :Event.cpp
* Auth : sjin
* Date : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "conio.h"
#include "windows.h"
HANDLE g_hEvent = NULL;
HANDLE g_hEvent2= NULL;
DWORD WINAPI ThreadSend( LPVOID pParam )
{
while( 1 )
{ //触发事件
SetEvent( g_hEvent );
Sleep( 500 );
SetEvent( g_hEvent2 );
Sleep( 500 );
}
return 0;
}
DWORD WINAPI ThreadRecv( LPVOID pParam )
{
while( 1 )
{ //等候事件通知
WaitForSingleObject( g_hEvent, INFINITE );
printf( "Hello Event: %p\n", g_hEvent );
}
return 0;
}
DWORD WINAPI ThreadRecv2( LPVOID pParam )
{
while( 1 )
{ //等候事件通知
WaitForSingleObject( g_hEvent2, INFINITE );
printf( "Hello Event2: %p\n", g_hEvent2 );
//g_hEvent2 设置为手动重置,如果没有这个函数,将无限制的执行这个操作。
ResetEvent( g_hEvent2 );
}
return 0;
}
void Create( )
{
DWORD nThreadID = 0;
HANDLE hThread[3] = { NULL };
hThread[0] = CreateThread( NULL, 0,ThreadSend, NULL, 0, &nThreadID );
hThread[1] = CreateThread( NULL, 0,ThreadRecv, NULL, 0, &nThreadID );
hThread[2] = CreateThread( NULL, 0,ThreadRecv2, NULL, 0, &nThreadID );
getch( );
TerminateThread(hThread[0],1);
TerminateThread(hThread[1],2);
TerminateThread(hThread[2],2);
//等待线程退出
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
printf("thread exit....");
}
int main(int argc, char* argv[])
{
//创建自动重置事件
g_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
//创建手动重置事件
g_hEvent2 = CreateEvent( NULL, TRUE, FALSE, NULL );
Create( );
getch( );
//关闭事件
CloseHandle( g_hEvent );
CloseHandle( g_hEvent2 );
return 0;
}
互斥量
1 互斥量
多个线程同时只能有一个执行.
2 互斥量使用
2.1 创建互斥
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全属性 BOOL bInitialOwner, //初始化的拥有线程 LPCTSTR lpName ); //名称
bInitialOwner - TRUE表示当前创建互斥
量的线程拥有互斥, FALSE为不拥有.
2.2 等候互斥
WaitForSingleObject
WaitForMultipleObjects
2.3 重置互斥
ReleaseMutex
2.4 关闭互斥
CloseHandle
2.5 使用互斥线程,按照谁先等候谁先拥有互斥量的规则顺序执行.
2.6 其他函数
OpenMutex 打开互斥
/* File :Mutex.cpp
* Auth : sjin
* DAte : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "conio.h"
#include "windows.h"
HANDLE g_hMutex = NULL;
DWORD WINAPI ThreadProc1( LPVOID pParam )
{
while( 1 )
{ //等候互斥量
WaitForSingleObject( g_hMutex, INFINITE );
printf( "ThreadProc1----------\n");
Sleep( 500 );
//释放互斥量
ReleaseMutex( g_hMutex );
}
return 0;
}
DWORD WINAPI ThreadProc2( LPVOID pParam )
{
while( 1 )
{ //等候互斥量
WaitForSingleObject( g_hMutex, INFINITE );
printf( "------ThreadProc2------\n");
Sleep( 500 );
//释放互斥量
ReleaseMutex( g_hMutex );
}
return 0;
}
DWORD WINAPI ThreadProc3( LPVOID pParam )
{
while( 1 )
{ //等候互斥量
WaitForSingleObject( g_hMutex, INFINITE );
printf( "------------ThreadProc3\n");
Sleep( 500 );
//释放互斥量
ReleaseMutex( g_hMutex );
}
return 0;
}
void Create( )
{
DWORD nThreadID = 0;
HANDLE hThread[3] = { NULL };
hThread[0] = CreateThread( NULL, 0, ThreadProc1, NULL, 0, &nThreadID );
hThread[1] = CreateThread( NULL, 0, ThreadProc2, NULL, 0, &nThreadID );
hThread[2] = CreateThread( NULL, 0, ThreadProc3, NULL, 0, &nThreadID );
getch( );
TerminateThread(hThread[0],1);
TerminateThread(hThread[1],2);
TerminateThread(hThread[2],2);
//等待线程退出
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
printf("thread exit....");
}
int main(int argc, char* argv[])
{ //创建互斥量
g_hMutex = CreateMutex( NULL,FALSE, NULL ); //FALSE表示当前创建互斥量的线程(main)不拥有互斥量
Create( );
getch( );
//关闭
CloseHandle( g_hMutex );
return 0;
}
七 信号量
1 信号量
通知的作用,和事件类似.但是与事件不同.
事件只维护一个值0或者1.
信号量维护一个变量,0时无信号,大于0有信号.
2 信号量的使用
2.1 创建信号量
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全属性 LONG lInitialCount,//初始信号量 LONG lMaximumCount,//最大信号量 LPCTSTR lpName //命名 );
返回创建好的信号量句柄.
2.2 等候信号量
WaitForSingleObject
WaitForMultipleObjects
2.3 释放信号
BOOL ReleaseSemaphore( HANDLE hSemaphore, //信号量句柄 LONG lReleaseCount,//释放信号的数量 LPLONG lpPreviousCount //释放前的数量 );
2.4 关闭信号量
CloseHandle
2.5 打开信号量
OpenSemaphore
/* File :Semaphore.cpp
* Auth : sjin
* DAte : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#include "conio.h"
#include "windows.h"
HANDLE g_hSemaphore = NULL;
DWORD WINAPI ThreadSend( LPVOID pParam )
{
while( 1 ){
CHAR ch = getch( );
switch( ch ){
case '1':
//释放信号 其实是增加可用空位
ReleaseSemaphore( g_hSemaphore, 1, NULL );
break;
case '5':
ReleaseSemaphore( g_hSemaphore, 5, NULL );
break;
}
}
return 0;
}
DWORD WINAPI ThreadRecv( LPVOID pParam )
{
while( 1 ){ //等候信号量的信号
WaitForSingleObject( g_hSemaphore, INFINITE );
printf( "Hello Semaphore\n" );
Sleep( 100 );
}
return 0;
}
void Create( )
{
DWORD nThreadID = 0;
HANDLE hThread[2] = { NULL };
hThread[0] = CreateThread( NULL, 0, ThreadSend, NULL, 0, &nThreadID );
hThread[1] = CreateThread( NULL, 0, ThreadRecv, NULL, 0, &nThreadID );
WaitForMultipleObjects( 2, hThread, TRUE, INFINITE );
}
int main(int argc, char* argv[])
{ //创建信号量
g_hSemaphore = CreateSemaphore( NULL, 3, 10, NULL ); //3表示一开始有3个可用空位
Create();
//关闭信号量
CloseHandle( g_hSemaphore );
return 0;
}
八 可等候定时器
1 可等候定时器
是一个更加精确系统提供的定时器.能够达到100ns级别.
2 定时器的使用
2.1 创建定时器
HANDLE CreateWaitableTimer( LPSECURITY_ATTRIBUTES lpTimerAttributes,//安全属性 BOOL bManualReset,//重置方式 LPCTSTR lpTimerName //命名 );
返回创建好的定时器的句柄
2.2 设置定时器
BOOL SetWaitableTimer( HANDLE hTimer, //定时器句柄 const LARGE_INTEGER *pDueTime,//定时器第一次触发的时间,100ns级别 LONG lPeriod, //后续每次触发的间隔,毫秒级别 PTIMERAPCROUTINE pfnCompletionRoutine, //APC处理函数 LPVOID lpArgToCompletionRoutine,//APC参数 BOOL fResume ); //休眠标识
pDueTime - 正值,表示绝对时间
负值,表示相对于现在的时间间隔
lPeriod - 0 定时器不再有后续触发
大于0 按照间隔触发
pDueTime | lPeriod | lPeriod ....
2.3 等候定时器
WaitForSingleObject
WaitForMultipleObjects
2.4 关闭定时器
CloseHandle
2.5 APC定时器 (异步调用处理)
/* File :WaitableTimer.cpp
* Auth : sjin
* DAte : 20140727
* Mail : 413977243@qq.com
*/
#include "stdafx.h"
#define _WIN32_WINNT 0x0400
#include "windows.h"
HANDLE g_hTimer = NULL;
DWORD WINAPI TimerThread( LPVOID pParam )
{
while( 1 ){
WaitForSingleObject( g_hTimer, INFINITE );
printf( "Hello Timer\n" );
}
return 0;
}
void Create( )
{ //创建定时器
g_hTimer = CreateWaitableTimer( NULL, FALSE, NULL );
//设置定时器
UINT64 nDueTime = -100000000;
//nDueTime是第一次触发的时间(纳秒),1000是以后触发间隔(毫秒)
SetWaitableTimer( g_hTimer, (PLARGE_INTEGER)&nDueTime, 1000, NULL, NULL, FALSE );
//创建等候线程
DWORD dwThreadID = 0;
HANDLE hThread = CreateThread( NULL, 0, TimerThread, NULL, 0, &dwThreadID );
WaitForSingleObject( hThread, INFINITE );
//关闭定时器
CloseHandle( g_hTimer );
}
VOID CALLBACK TimerProc( LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue )
{
printf( "------APC TimerProc--------\n" );
}
void APCTimer( )
{ //创建定时器
HANDLE hTimer = CreateWaitableTimer(
NULL, FALSE, NULL );
//设置定时器
UINT64 nDueTime = -10000000;
SetWaitableTimer( hTimer, (PLARGE_INTEGER)&nDueTime, 1000, TimerProc, NULL, FALSE );
//
while( 1 )
{
SleepEx( -1, TRUE ); //阻塞main()函数,但是waitabletimer的消息还是能执行。说明这是另外一个消息队列
}
//关闭句柄
CloseHandle( hTimer );
}
int main(int argc, char* argv[])
{
Create( );
APCTimer( );
return 0;
}