`

经典线程同步 关键段CS

 
阅读更多

本文参考http://blog.csdn.net/morewindows/article/details/7442639

 

关键段CRITICAL_SECTION一共就四个函数,使用很是方便。下面是这四个函数的原型和使用说明。

 

函数功能:初始化

函数原型:

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

函数说明:定义关键段变量后必须先初始化。

 

函数功能:销毁

函数原型:

void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

函数说明:用完之后记得销毁。

 

函数功能:进入关键区域

函数原型:

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

函数说明:系统保证各线程互斥的进入关键区域。

 

函数功能:离开关关键区域

函数原型:

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

 

然后在经典多线程问题中设置二个关键区域。一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时。代码如下:

 

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

using namespace std;

const int THREADNUM = 30;
volatile long number = 0;

CRITICAL_SECTION threadPM,threadCode;

unsigned int __stdcall threadFunc(PVOID pM) {
        int nThreadNum = *(int *)pPM;
	LeaveCriticalSection(&threadPM);
	Sleep(100);
	EnterCriticalSection(&threadCode);
	cout << nThreadNum  << endl;
	number++;
	Sleep(0);
	LeaveCriticalSection(&threadCode);
	return 0;
}

int main() {
	int num = 20;
	
	HANDLE handle[THREADNUM];

	InitializeCriticalSection(&threadCode);
	InitializeCriticalSection(&threadPM);

	number = 0;
	for(int i=0; i< THREADNUM; i++) {
		EnterCriticalSection(&threadPM);
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
	}
	
	WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); //安全数量为64
	Sleep(500);
	cout << "计数个数为" << number << endl;
	
	DeleteCriticalSection(&threadCode);
	DeleteCriticalSection(&threadPM);
	getchar();
	return 0;
}

 

 

输出结果为:



我们发现,各个子线程之间已经可以互斥的访问全局变量了,但是主线程和子线程之间的同步关系出现了问题

 

       这是为什么呢?

要解开这个迷,最直接的方法就是先在程序中加上断点来查看程序的运行流程。断点处置示意如下:

 


 

 

然后按F5进行调试,正常来说这两个断点应该是依次轮流执行,但实际调试时却发现不是如此,主线程可以多次通过第一个断点即

       EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域

这一语句。这说明主线程能多次进入这个关键区域!

 

 

关键段的本质是保证线程之间的互斥访问,对于同步访问是不能用关键段的。

 

 

先找到关键段CRITICAL_SECTION的定义吧,它在WinBase.h中被定义成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中声明,它其实是个结构体:

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;

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

 

第三个参数:LONGRecursionCount;  

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

 

第四个参数:HANDLEOwningThread;  

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

 

第五个参数:HANDLELockSemaphore;

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

 

第六个参数:DWORDSpinCount;    

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

 

 

 

由这个结构可以知道关键段会记录拥有该关键段的线程句柄即关键段是有“线程所有权”概念的。事实上它会用第四个参数OwningThread来记录获准进入关键区域的线程句柄,如果这个线程再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。

即同一个线程可以重复进入,而其他线程必须等到关键区全部释放后才能轮到自己执行。

其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0时,系统会自动更新关键段并将等待中的线程换回可调度状态。

因此可以将关键段比作旅馆的房卡,调用EnterCriticalSection()即申请房卡,得到房卡后自己当然是可以多次进出房间的,在你调用LeaveCriticalSection()交出房卡之前,别人自然是无法进入该房间。

回到这个经典线程同步问题上,主线程正是由于拥有“线程所有权”即房卡,所以它可以重复进入关键代码区域从而导致子线程在接收参数之前主线程就已经修改了这个参数。所以关键段可以用于线程间的互斥,但不可以用于同步。

 

 

 

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

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

函数原型:

BOOLInitializeCriticalSectionAndSpinCount(

  LPCRITICAL_SECTION lpCriticalSection,

  DWORD dwSpinCount);

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

 

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

函数原型:

DWORDSetCriticalSectionSpinCount(

  LPCRITICAL_SECTION lpCriticalSection,

  DWORD dwSpinCount);

 

《Windows核心编程》第五版的第八章推荐在使用关键段的时候同时使用旋转锁,这样有助于提高性能。

值得注意的是如果主机只有一个处理器,那么设置旋转锁是无效的。如果在单线程的机器上调用这个函数,那么函数会忽略dwSpinCount参数,因此次数总是为0。因为在单处理器的机器上设置循环次数毫无用处:如果一个线程正在循环,那么占用资源的线程将没有机会放弃对资源的访问权限。

难点在于如何确定传给dwSpinCount参数的值。为了得到最佳的性能,最简单的方法就是尝试各种数值,直到对性能感到满意为止。用来保护进程堆的关键段所使用的旋转次数大约是4000,这可以作为我们的一个参考值。旋转锁的原理和定义请参考《旋转锁的解释

 

无法进入关键区域的线程总会被系统将其切换到等待状态。

 

最后总结下关键段:

1.关键段共初始化化、销毁、进入和离开关键区域四个函数。

2.关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。

3.推荐关键段与旋转锁配合使用。

 

  • 大小: 16.2 KB
  • 大小: 13.4 KB
分享到:
评论

相关推荐

    VC6 多线程的串口操作,有线程同步的解决方法(terminal).zip

    线程同步是确保多个线程在访问共享资源时按预期顺序执行的关键技术。在C++中,可以使用各种同步机制,包括临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)以及事件(Event)。这些同步对象允许我们创建...

    Visual+C++线程同步技术剖析

    临界区是实现线程同步的一种常见机制,用于确保某一时刻只有一个线程能够访问特定的共享资源或执行特定的代码段。临界区的实现依赖于`CRITICAL_SECTION`结构体,通过调用`EnterCriticalSection()`和`...

    Visual C++线程同步技术剖析

    然而,多线程的应用也带来了一系列的问题,其中最为关键的就是线程同步问题。线程同步是指在多线程环境下,为确保程序正确执行而采取的一系列措施。Visual C++作为一款广泛使用的开发工具,提供了丰富的线程同步机制...

    Visual C++临界区域线程同步工程

    临界区域(Critical Section)是线程同步的一种常见机制,它确保同一时间只有一个线程能够访问特定的共享资源或代码段,从而避免了数据竞争和其他并发问题。在Windows操作系统中,Visual C++提供了对临界区域的支持...

    windows下多核多线程编程

    本文将深入探讨如何在Windows环境下进行多线程同步,并以实际的示例代码进行解析。 首先,我们需要了解什么是线程。线程是进程中的一个执行单元,每个进程至少包含一个线程,而多线程则意味着在一个进程中可以有多...

    精选_线程同步之临界区_源码打包

    线程同步是多线程编程中的关键概念,用于确保在多线程环境下,对共享资源的访问能够有序进行,防止数据竞争和不一致状态的发生。临界区是实现线程同步的一种基本方法,它允许一次只有一个线程进入,从而确保在特定...

    MFC CCriticalSection 关键段例子

    关键段是一种线程同步原语,当一个线程进入关键段后,其他试图进入的线程会被阻塞,直到当前线程离开关键段。`CCriticalSection`在MFC中扮演的角色就是这样的一个互斥锁,用于保护对共享数据的访问。 在描述中提到...

    线程同步

    线程同步是多线程编程中的关键环节,临界区作为Windows系统提供的基本同步工具之一,其高效性和简单性使其成为解决资源共享问题的首选方案。然而,合理设计和使用临界区,避免死锁和不必要的线程阻塞,是确保程序...

    关于线程同步等待的两种代码示例

    在多线程编程中,线程同步是一种关键的技术,它用于控制多个线程对共享资源的访问,确保数据的一致性和避免竞态条件。本文将深入探讨如何使用两种不同的方法来实现线程同步等待,主要关注"Event"方式。我们将通过...

    多线程下写入链表的同步问题

    通过在关键代码段前后分别调用`EnterCriticalSection`和`LeaveCriticalSection`,可以确保同一时间只有一个线程能够访问临界区内的代码。 示例代码如下: ```cpp DWORD WINAPI MyThreadFunc(LPVOID lpParam) { ...

    VC++ 使用临界区同步线程 实例

    在VC++环境中,我们可以利用Windows API提供的临界区功能来实现线程同步。下面将详细解释如何在VC++中使用临界区同步线程。 首先,了解临界区的基本概念。临界区是一段代码,它在任何时候只能被一个线程执行。当一...

    CS开发多线程资料

    在CS(计算机科学)开发中,多线程是不可或缺的一部分,尤其对于创建复杂的、高性能的应用程序至关重要。初学者在学习多线程时,首先要理解其基本概念。 多线程是指在一个应用程序中存在多个并发执行的线程。这些...

    随手做一个多线程的 CS架构的 文件传输Demo

    标题中的“随手做一个多线程的 CS架构的 文件传输Demo”指的是创建一个基于客户端-服务器(Client-Server,CS)架构的多线程文件传输示例。在这个项目中,我们将探讨如何利用多线程技术来提高文件传输的效率,以及在...

    用多线程同步方法解决哲学家就餐问题报告.docx

    【课程设计报告】\n\n课程设计的主题是“用多线程同步方法解决哲学家就餐问题”,这是一个经典的并发控制问题,常用于考察操作系统中的死锁预防和资源分配策略。哲学家就餐问题描述了五个哲学家坐在一张圆桌旁,每个...

    采用全局变量方式实现多线程的通信

    在MFC框架中,我们可以使用`CSingleLock`或`CMultiLock`类来实现线程同步。`CSingleLock`适用于保护单个资源,而`CMultiLock`可以锁定多个资源。这些类提供了锁定和解锁资源的方法,确保在执行关键操作时,全局变量...

    C#多线程实例软件开发

    3. **线程同步机制**:除了Monitor和lock,C#还提供了其他同步工具,如`Mutex`、`Semaphore`、`Monitor.Pulse`/`Monitor.Wait`等。这些工具可用于更复杂的线程控制场景。 4. **线程状态管理**:C#提供了一些方法来...

    多线程编程例子

    2. 线程同步:避免线程间的竞态条件,可以使用互斥锁(`pthread_mutex_t`)进行临界区保护,或者使用条件变量(`pthread_cond_t`)进行同步。 3. 线程通信:线程间可以通过共享内存、信号量(semaphore)或消息队列...

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

    互斥非常类似于临界区,除了两个关键的区别:首先,互斥可用于跨进程的线程同步。其次,互斥能被赋予一个字符串名字,并且通过引用此名字创建现有互斥对象的附加句柄。 提示临界区与事件对象(比如互斥对象)的最大的...

    采用_beginthreadex创建多线程

    `WaitFor`系列函数,如`WaitForSingleObject`或`WaitForMultipleObjects`,在多线程协作中也十分关键。它们允许线程等待特定的事件(如另一个线程完成工作、信号量计数达到某个值等),直到条件满足才会继续执行。 ...

Global site tag (gtag.js) - Google Analytics