多线程相关感悟,摘抄,面试题

一:

1 CRITICAL_SECTION  以及事件 学习 相关   下面是一个使用临界区例子:

#include <stdio.h>
#include <process.h>
#include <windows.h>
CRITICAL_SECTION g_cs;
const int num=2;
int count;
unsigned __stdcall ThreadFun(void* par);

int main()
{
	int i;
    count=0;
    HANDLE handles[num];
    InitializeCriticalSection(&g_cs);
    for(i=0;i<num;++i)
    {
		EnterCriticalSection(&g_cs);
                handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,0);
		if(num == i + 1 ) 	printf("完毕\n");
		LeaveCriticalSection(&g_cs);
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(num,handles,TRUE,INFINITE);
    for(i=0;i<num;++i)
        CloseHandle(handles[i]);
    DeleteCriticalSection(&g_cs);
    
    return 0;
}
unsigned __stdcall ThreadFun(void* par)
{
	int k = *(int *)par;
    EnterCriticalSection(&g_cs);
    for(int i=0;i<10;++i)
       printf("%d %d\n",k+1 ,++count);
    count=0;
	LeaveCriticalSection(&g_cs);
    return 0;
}



如上    主线程建立10个子线程调用ThreadFun函数    打印结果应该如下   

1   1

1   2

1   3

。。

1  10

2   1

2   2

。。。

2   10


但是真实的结果却是


注 : 如果vc6.0显示error C2065: '_beginthreadex' : undeclared identifier  可以  工程-》设置-》c/c++-》分类中下拉选项选择Code Generation-》Use run time library 选择MultiThread 即可

可以看出各子线程已经可以互斥的访问与输出全局资源(右边的 1 2 3 ...10),但主线程与子线程之间的同步还是有点问题(左边的一堆2 )。主线程与子线程之间的同步是指:当一个子线程结束后主线程才继续建立一个新的线程,而上面的代码明显没有达到要求。

上例中创建了2个线程, 而在输出的时候 i的值有改变(全是2)  是因为在子线程中

int i = *(int*)pParam;  这句话之前  传递过去的i的值可能就已经改变了  因为是传地址 子线程中得到的i可能就不是我们希望的i值了   
参考二中的第2条! 


不能同步原因 :CRITICAL_SECTION的对象是一个结构体  其OwningThread记录着这个对象g_cs属于哪个线程   而这个线程可以多次进入临界区 并记录进入次数。如果这个线程(本例中为主线程)再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程(本例中为主线程)调用LeaveCriticalSection()使其进入的次数为0时(此时拥有者为0即没有拥有者),系统会自动更新关键段并将等待中的线程换回可调度状态。这时候关键段的拥有者也将发生变化。拥有者可以在没有离开临界区的情况下多次进入临界区! 关键段可以用于线程间的互斥,但不可以用于同步。

注意临界区的一个使用小陷阱  一定要让一个线程中的Enter 和Leave 执行次数相同 否则其它线程将无法运行  当自己的程序不能运行其它线程的时候就要考虑下是不是这个原因   反正我是中招了

还有一个容易用错的地方: 一定不要让Enter Leave包括的范围之外有多个线程的共享变量!

如下段代码

#include <stdio.h>
#include <process.h>
#include <windows.h>
CRITICAL_SECTION g_cs;
unsigned __stdcall ThreadFun(void* par);

int main()
{
    InitializeCriticalSection(&g_cs);
      _beginthreadex(NULL,0,ThreadFun,NULL,0,0);
    Sleep(100000);
    DeleteCriticalSection(&g_cs);
    return 0;
}
unsigned __stdcall ThreadFun(void* par)
{
	for(int i=0;i<10;++i)
	{
		EnterCriticalSection(&g_cs);
		printf("我来了\n");
		//LeaveCriticalSection(&g_cs);	
	}
	return 0;
}

将输出10个 “我来了”。这是一个线程拥有者多次进入临界区 如果是多个线程之间其它线程就需要等待某个目前拥有临界区的线程LeaveCriticalSection 。即只有等到g_cs的拥有线程执行了相同次数的进入 离开临界区 而不再拥有g_cs的时候其它线程才能够拥有g_Cs 否则就要一直等待!

上例中由于没有leave   那么如果其它线程想要Enter是不可能的了   这也是临界区一个大的缺点即遗弃问题! 由于它忘记leave而导致别人一直无法enter


typedef struct _RTL_CRITICAL_SECTION {

    PRTL_CRITICAL_SECTION_DEBUGDebugInfo;

    LONGLockCount;

    LONGRecursionCount;

    HANDLEOwningThread; // from the thread's ClientId->UniqueThread

    HANDLELockSemaphore;

    DWORDSpinCount;

RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

各个参数的解释如下:

第一个参数:PRTL_CRITICAL_SECTION_DEBUGDebugInfo;

调试用的。

 

第二个参数:LONGLockCount;

初始化为-1n表示有n个线程在等待。

 

第三个参数:LONGRecursionCount;  

表示该关键段的拥有线程对此资源获得关键段次数,初为0

 

第四个参数:HANDLEOwningThread;  

即拥有该关键段的线程句柄,微软对其注释为——from the thread's ClientId->UniqueThread

 

第五个参数:HANDLELockSemaphore;

实际上是一个自复位事件。

 

第六个参数:DWORDSpinCount;    

旋转锁的设置,单CPU下忽略

 


由于将线程切换到等待状态的开销较大,因此为了提高关键段的性能,Microsoft将旋转锁合并到关键段中,这样EnterCriticalSection()会先用一个旋转锁不断循环,尝试一段时间才会将线程切换到等待状态。下面是配合了旋转锁的关键段初始化函数

函数功能:初始化关键段并设置旋转次数

函数原型:

BOOLInitializeCriticalSectionAndSpinCount(

  LPCRITICAL_SECTIONlpCriticalSection,

  DWORDdwSpinCount);

函数说明:旋转次数一般设置为4000

 

函数功能:修改关键段的旋转次数

函数原型:

DWORDSetCriticalSectionSpinCount(

  LPCRITICAL_SECTIONlpCriticalSection,

  DWORDdwSpinCount);

Windows 核心编程》第五版的第八章推荐在使用关键段的时候同时使用旋转锁,这样有助于提高性能。值得注意的是如果主机只有一个处理器,那么设置旋转锁是无效的。无法进入关键区域的线程总会被系统将其切换到等待状态。


2  上面的解决同步问题的方法为使用事件

#include <stdio.h>
#include <process.h>
#include <windows.h>
//CRITICAL_SECTION g_cs;
const int num=20;
int count;
unsigned __stdcall ThreadFun(void* par);
HANDLE g_hEvent;
int main()
{
	int i;
    count=0;
    HANDLE handles[num];
   // InitializeCriticalSection(&g_cs);
	g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建事件 第二个参数为FALSE 表示自动置位 
    for(i=0;i<num;++i)
    {
        handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,0);
	    WaitForSingleObject(g_hEvent, INFINITE); //等待事件被触发 因为是自动置位会调用 ResetEvent 使事件变成未触发状态    
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(num,handles,TRUE,INFINITE);
    for(i=0;i<num;++i)
        CloseHandle(handles[i]);
   // DeleteCriticalSection(&g_cs);
    return 0;
}
unsigned __stdcall ThreadFun(void* par)
{
	int k = *(int *)par;
   // EnterCriticalSection(&g_cs);
    for(int i=0;i<10;++i)
       printf("%d %d\n",k+1 ,++count);
    count=0;
	SetEvent(g_hEvent); //   触发事件
	//LeaveCriticalSection(&g_cs);
    return 0;
}



注意 CreateEvent  中

第一个参数表示安全控制,一般直接传入NULL

第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。 

第三个参数表示事件的初始状态,传入TRUR表示已触发。

第四个参数表示事件的名称,传入NULL表示匿名事件。

关于事件的另一个函数

第二个 OpenEvent

函数功能:根据名称获得一个事件句柄。

函数原型:

HANDLEOpenEvent(

 DWORDdwDesiredAccess,

 BOOLbInheritHandle,

 LPCTSTRlpName     //名称

);

函数说明:

第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。

第二个参数表示事件句柄继承性,一般传入TRUE即可。

第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

PulseEvent

函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。

函数原型:BOOLPulseEvent(HANDLEhEvent);

函数说明:此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。注意是正处于

此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态

一个很好的例子请参考 http://blog.youkuaiyun.com/morewindows/article/details/7445233 这篇文章的最下面!

/****************************************华丽丽的分割线********************************************/

二  一道面试题

1   写一个多线程程序  :   子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码。

#include <stdio.h>
#include <process.h>  
#include <windows.h>
/***********************************/
/*       by hnust_xiehonghao       */
/*            2014.8.1             */
/*   为了方便观察我们让整个程序共  */
/*   运行10次每次让A运行2次让B5次  */
/***********************************/                    

CRITICAL_SECTION g_cs;
HANDLE g_hAEvent,g_hBEvent; 
int count = 0;
unsigned int __stdcall FuncA(void *pParam)  
{
	for(int i = 0; i< 10; i++)
	{
		WaitForSingleObject(g_hAEvent, INFINITE);//重要! 等待A事件被触发 否则不运行
		EnterCriticalSection(&g_cs);
		int j;
		for(j = 0 ;j < 2; j++)
		{
			count++;  
			printf("A %d\n",  count);
		}
		SetEvent(g_hBEvent);
		ResetEvent(g_hAEvent);
		LeaveCriticalSection(&g_cs); 
		Sleep(10);
	}
	return 0;
}

unsigned int __stdcall FuncB(void *pParam)  
{
		
	for(int i = 0; i < 10; i++)
	{
		WaitForSingleObject(g_hBEvent, INFINITE);//重要!等待B事件被触发 否则不运行
		EnterCriticalSection(&g_cs);
	
		int j;
		for(j = 0 ;j < 5; j++)
		{
			count++;
			printf("B %d\n", count);
		}
		SetEvent(g_hAEvent);
		ResetEvent(g_hBEvent);
		LeaveCriticalSection(&g_cs);
		Sleep(10);
	}
	return 0;
}

int main()
{
	int i;
	HANDLE handle;
    InitializeCriticalSection(&g_cs);  

	g_hAEvent = CreateEvent(NULL, TRUE, TRUE, NULL); 
	g_hBEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	handle = (HANDLE)_beginthreadex(NULL, 0, FuncA, NULL, 0, NULL); 
 	handle = (HANDLE)_beginthreadex(NULL, 0, FuncB, NULL ,0 , NULL); 

	Sleep(1000000);
	CloseHandle(g_hAEvent); 
	CloseHandle(g_hBEvent); 
    DeleteCriticalSection(&g_cs); 
	return 0;
}


以上为个人所写 如有差误  请留言

2   一个易错的小地方

	int flag = 0;
	handle = (HANDLE)_beginthreadex(NULL, 0, FuncA, &flag, 0, NULL); 
	Sleep(100); flag = 1;// 如果加上这2句话  那么当FuncA运行的时候传递过去的参数一开始是0,那么运行一会就会变成了1
	//解决方法: 我们可以定义一个int k = flag  传递k过去 而不去修改k
因为传递的是flag的地址,而在子线程接收这个值之前,flag的值可能就已经变了  此处会变成1  

/*************************************华丽丽的分割线*********************************************/

三 :插播一个linux中进程相关的题目  

/*请解释下面程序的输出 */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
     pid_t pid1;
     pid_t pid2;
     pid1 = fork();
     pid2 = fork();
     printf("pid1:%d, pid2:%d\n", pid1, pid2);
}
/*
输出:
pid1:3411, pid2:3412
pid1:0, pid2:3413
pid1:3411, pid2:0
pid1:0, pid2:0
*/

如果不懂可以参考http://blog.youkuaiyun.com/jason314/article/details/5640969  这是一篇很屌的文章!


/***************************************华丽丽的分割线*******************************************/

四: _beginthreadex createthread 的区别

1 首先对于_beginthreadex会调用后者  不过会在调用之前做一些其他的操作!

  考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于多线程会出什么事,你一定知道的了。故必须存在一种机制,使得每个线程能够引用它自己的errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址(自己看去吧)。 

 结束线程使用函数_endthreadex函数,释放掉线程的tiddata数据块。   但是return 返回的时候 会自动调用这个  不需要手动调用!(手动调用也会产生副作用 最好让它自动退出)

即__beginthreadex 生成线程后 不必考虑其它的了  退出时会自动释放资源 关闭句柄  

2  对于CreateThread  

有些CRT的函数(CRT函数就是标准的C语言函数)象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有Memory   Leak,在线程频繁启动的软件中(比如某些服务器软件),迟早会让系统的内存资源耗尽!   

3   注意对于2者   都不能忘记closehandle!

/***************************************华丽丽的分割线*******************************************/

五: windows下多线程的调试方法及步骤

Windows下的VC对多程序的调试比较简单。如果想要对程序进行调试的话,首先F10,开始运行程序。其次,我们需要等线程创建之后才能设置断点,不然我们看到的程序只有main函数一个thread。
1. 单击【Debug】,选择【threads】,那么我们就可以开始多线程调试了;
2. 如果需要对某一个thread挂起,单击对应的thread,选择【suspend】即可;
3. 如果需要对某一个thread重新调度,单击对应的thread,选择【resume】即可;
4. 如果需要查看特定thread的堆栈,那么选择那个thread,然后【Set Focus】,关闭threads对话框,在堆栈窗口中即可看到;
5. 如果某个线程被挂住,那么此时所有的线程都挂住了,如果你step运行,所有的threads都会参与运行;
6. 如果需要对某一个thread进行调试,那么需要对其他的thread进行suspend处理 。


/***************************************华丽丽的分割线*******************************************/

六 : 原子操作 Interlocked系列互锁函数

假设 一个long k = 0;  有多个线程对它进行访问 进行 +1 操作 不管 k++  还是 ++k 都会得到不正确的结果! 因此我们需要对它进行原子操作 当然也有其他的方法 

原子操作的方法如下   :InterlockedIncrement((LPLONG)&k);

另外   我们要将k 定义为volatile类型!

volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.

//增减

  (1) LONG InterlockedIncrement(IN OUT LONG volatile *lpAddend);   
  lpAddend为长整型变量的地址,返回值为原始值。这个函数的主要作用是原子性自增(相当于++操作)。
  (2) LONG InterlockedDecrement(IN OUT LONG volatile *lpAddend);
  lpAddend为长整型变量的地址,返回值为原始值。这个函数的主要作用是原子性自减(相当于--操作)。
  (3) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
  Addend为长整型变量的地址,Increment为想要在Addend指向的长整型变量上增加的数值(可以是负数)。这个函数的主要作用是保证这个加操作为一个原子访问。
//交换
  (4) LONG InterlockedExchange( LPLONG Target, LONG Value );
  用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
  (5) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
  用第二个参数的值取代第一个参数指向的值。函数返回值为原始值。
//比较交换
  (6) LONG InterlockedCompareExchange(
  LPLONG Destination, LONG Exchange, LONG Comperand );
  如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
  (7) PVOID InterlockedCompareExchangePointer (
  PVOID *Destination, PVOID Exchange, PVOID Comperand );
  如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。

/***************************************华丽丽的分割线*******************************************/

七:小概念

1 线程创建处理后不是马上就执行,要等系统的调度。所以有可能后创建处理的线程先执行。

2 只有互斥量能够处理遗弃问题 ,而其它如临界区 信号量 事件都不能处理遗弃问题  所谓遗弃是指当拥有互斥量的线程没有释放互斥量就结束了

具体可以参考http://blog.youkuaiyun.com/morewindows/article/details/7823572    秒杀多线程第十五篇 关键段,事件,互斥量,信号量的“遗弃”问题 。

/***************************************华丽丽的分割线*******************************************/

八:  互斥量的学习

互斥量是一个内核对象,它用来确保一个线程独占一个资源的访问。

互斥量与关键段的行为相似,区别如下:

<1. Mutexes 操作要比 Critical Section 费时的多。

<2. Mutexes 可以跨进程使用,Critical Section 则只能在同一进程中使用。

<3. 等待一个 Mutex 时,你可以指定"结束等待"的时间长度,而 Critical Section 则不行。

<4 遗弃的问题。具体看下面讲述


1 创建mutex

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性,默认为NULL

BOOL bInitialOwner, // initial owner

LPCTSTR lpName // mutex 的名称,是一个字符串

);

对于第二个参数:如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态。如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,这意味互斥量不为任何线程占用,处于触发状态。


2 打开一个mutex

HANDLE OpenMutex(

DWORD dwDesiredAccess, //表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS

BOOL bInheritHandle, //表示互斥量句柄继承性,一般传入TRUE

LPCTSTR lpName // 名称

);


3 触发互斥量

BOOL ReleaseMutex(

HANDLE hMutex // handle to mutex

);

访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,其它线程可以开始访问了。


调用过程如下:

CreateMutex(); //创建

WaitForXXXObject(); //等待

ReleaseMutex(); //释放

CloseHandle(); //关闭(释放方法和所有内核对象都一样直接CloseHandle)


下面是互斥量的一个应用  修改自http://www.cnblogs.com/kennyMc/archive/2012/12/15/2818887.html 这篇博客很好

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

const int num=20;
int count;
HANDLE ThreadMutex;
 
unsigned __stdcall ThreadFun(void* par)
{
    //等待互斥量对象(内部检查互斥量对象的线程ID是是否为0,0为触发状态)
    //如果线程ID不为0,那么调用线程将进入等待状态
	int k = *(int *)par;
    WaitForSingleObject(ThreadMutex,INFINITE);//等待互斥量被触发  之后占有互斥量 不让它处于触发状态
    for(int i=0;i<10;++i)
        printf("线程%d  %d\n",k, ++count);
    count=0;
    //释放对资源的所有权,将互斥量对象的线程ID和递归计数设置成0
    ReleaseMutex(ThreadMutex);
    return 0;
}
int main()
{
	int i;
    count=0;
    HANDLE handles[num];
    //互斥量对象的线程ID和递归计数初始化为0,互斥量想在不为任何线程占用
    ThreadMutex=CreateMutex(NULL,FALSE,"hehe");
    for( i=0;i<num;++i)
    {
        handles[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,NULL);
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(num,handles,TRUE,INFINITE);
    for(i=0;i<num;++i)
        CloseHandle(handles[i]);
    CloseHandle(ThreadMutex);
 
    return 0;
}
 
由输出结果可以看出子线程和主线程不能同步  因为它也有线程从属的问题


与关键段类似,互斥量也是不能解决线程间的同步问题 也具有线程归属权。具体参考 本文第一题 第一个代码例子



另外由于互斥量常用于多进程之间的线程互斥,所以它比关键段还多一个很有用的特性——“遗弃”情况的处理。比如有一个占用互斥量的线程在调用ReleaseMutex()触发互斥量前就意外终止了(相当于该互斥量被“遗弃”了),那么所有等待这个互斥量的线程是否会由于该互斥量无法被触发而陷入一个无穷的等待过程中了?这显然不合理。因为占用某个互斥量的线程既然终止了那足以证明它不再使用被该互斥量保护的资源,所以这些资源完全并且应当被其它线程来使用。因此在这种“遗弃”情况下,系统自动把该互斥量内部的线程ID设置为0,并将它的递归计数器复置为0,表示这个互斥量被触发了。然后系统将公平地选定一个等待线程来完成调度(被选中的线程的WaitForSingleObject()会返回WAIT_ABANDONED_0)。 而临界区却无法解决遗弃的问题


互斥量 经常用于不同进程之中!    如一个进程等待另一个进程进行释放

另外互斥量常用于程序之生成一个实例   每次打开一个程序都会运行以下代码 第一次正常 而第二次无法创建相同名字的互斥量  会发生已经存在的错误  :

	//实现只有一个实例运行
	HANDLE   hMutex=::CreateMutex(NULL,TRUE,"DocCollector");//FirstName可以随便取一个唯一的名字   
	if   (hMutex!=NULL)   
	{   
		if (GetLastError()==ERROR_ALREADY_EXISTS)   
		{   
			//让其无任何反应
			AfxMessageBox("已经有一个程序运行.");   
			return   FALSE;   
		}   
	} 

/***************************************华丽丽的分割线*******************************************/

九 :  信号量的学习

//创建信号量
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全控制,一般直接传入NULL
  LONG lInitialCount,//初始资源数量。
  LONG lMaximumCount,//最大并发数量。
  LPCTSTR lpName//信号量的名称,传入NULL表示匿名信号量。

);

//打开信号量
HANDLE OpenSemaphore(
  DWORD dwDesiredAccess,//访问权限,对一般传入SEMAPHORE_ALL_ACCESS。
  BOOL bInheritHandle,//信号量句柄继承性,一般传入TRUE
  LPCTSTR lpName//名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
);

//递增信号量的当前资源计数
BOOL ReleaseSemaphore(
  HANDLE hSemaphore,//信号量的句柄
  LONG lReleaseCount,  //增加个数,必须大于0且不超过最大资源数量。
  LPLONG lpPreviousCount //可以用来传出先前的资源计数,设为NULL表示不需要传出。
);
//关闭信号量
CloseHandle();
//注意:当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。
//在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),
//减1后返回让调用线程继续执行。一个线程可以多次调用等待函数来减小信号量。 

WaitForXXXObject()  会让信号量减小  WaitForSingleObject() 会让信号量减一

注意mutex不是semaphore的一种退化。 因为信号量没有所有权的观念 ! 和mutex不同!

应用:     

用信号量实现同步    完成最上面的第一题

#include <stdio.h>
#include <process.h>
#include <windows.h>

//信号量与关键段
HANDLE            g_hSemaphore;
CRITICAL_SECTION  g_csThreadCode;
int g_Num = 0;

unsigned int __stdcall Fun(void *pVoid)
{
	int k = *(int *)pVoid;
	EnterCriticalSection(&g_csThreadCode);
	++g_Num;
	printf("编号%d  全局资源值为%d\n", k, g_Num);
	ReleaseSemaphore(g_hSemaphore, 1, NULL);//信号量++
	LeaveCriticalSection(&g_csThreadCode);
	return 0;
}
int main()
{
	//创建信号量
        g_hSemaphore = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问
	InitializeCriticalSection(&g_csThreadCode);

	HANDLE  handle[22];	
	for(int i = 0; i < 10; i++)
	{
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
		WaitForSingleObject(g_hSemaphore, INFINITE);//等待信号量>0 若大于0 则将信号量减一 继续向下运行
	}
	WaitForMultipleObjects(10, handle, TRUE, INFINITE);
	DeleteCriticalSection(&g_csThreadCode);
	CloseHandle(g_hSemaphore);
	for (i = 0; i < 10; i++)
		CloseHandle(handle[i]);
	return 0;
}

/***************************************华丽丽的分割线*******************************************/

十 关于互斥与同步 以及以上互斥和同步机制的比较

以下内容直接摘抄自http://blog.youkuaiyun.com/morewindows/article/details/7538247               MoreWindows

感谢这个博客  好帅   让我慢慢走近了多线程的应用!    

1.线程(进程)同步的主要任务

答:在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。

 

2.线程(进程)之间的制约关系?

当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。

1).间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。

2).直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。

间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步

看完概念性知识,下面用几个表格来帮助大家更好的记忆和运用多线程同步互斥的四个实现方法——关键段、事件、互斥量、信号量。

 

关键段CS与互斥量Mutex

 

创建或初始化

销毁

进入互斥区域

离开互斥区域

关键段CS

Initialize-

CriticalSection

Delete-

CriticalSection

Enter-

CriticalSection

Leave-

CriticalSection

互斥量Mutex

CreateMutex

CloseHandle

等待系列函数如WaitForSingleObject

ReleaseMutex

关键段与互斥量都有“线程所有权”概念,可以将“线程所有权”理解成旅馆的房卡,在旅馆前台登记名字拥有房卡后是可以多次进出房间的,其它人则无法进入直到你交出房卡。每个线程必须先通过EnterCriticalSectionWaitForSingleObject来尝试获得“线程所有权”才能调用LeaveCriticalSectionReleaseMutex。否则会调用失败,这就相当于伪造房卡去办理退房手续——由于登记本上没有你的名字所以会被拒绝。

互斥量能很好的处理“遗弃”情况,因此在多进程之间可以放心的使用。

 

事件Event

 

创建

销毁

使事件触发

使事件未触发

事件Event

CreateEvent

CloseHandle

SetEvent

ResetEvent

注意事件的手动置位和自动置位要分清楚,不要混淆了。

 

信号量Semaphore

 

创建

销毁

递减计数

递增计数

信号量

Semaphore

Create-

Semaphore

CloseHandle

等待系列函数如WaitForSingleObject

Release-

Semaphore

信号量在计数大于0时表示触发状态,调用WaitForSingleObject不会阻塞,等于0表示未触发状态,调用WaitForSingleObject会阻塞直到有其它线程递增了计数。

 

注意:互斥量,事件,信号量都是内核对象,可以跨进程使用(通过OpenMutexOpenEventOpenSemaphore)。


本文此条(十)内容直接摘抄自http://blog.youkuaiyun.com/morewindows/article/details/7538247               MoreWindows

/***************************************华丽丽的分割线*******************************************/

十一:待续





By  hnust_xiehonghao  转载请注明

参考:

http://blog.youkuaiyun.com/morewindows/article/details/7442639

http://mxpan.blog.sohu.com/153608222.html

http://blog.youkuaiyun.com/kafeiwuzhuren/article/details/6597711

http://blog.youkuaiyun.com/lwbeyond/article/details/7617234

http://blog.youkuaiyun.com/windows_nt/article/details/9456643

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值