秒杀多线程第五篇 经典线程同步 关键段CS

上一篇《秒杀多线程第四篇 一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题。

本文首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现机制与原理。

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

 

函数功能:初始化

函数原型:

void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

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

 

函数功能:销毁

函数原型:

void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

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

 

函数功能:进入关键区域

函数原型:

void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

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

 

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

函数原型:

void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);

 

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

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <process.h>  
  3. #include <windows.h>  
  4. long g_nNum;  
  5. unsigned int __stdcall Fun(void *pPM);  
  6. const int THREAD_NUM = 10;  
  7. //关键段变量声明  
  8. CRITICAL_SECTION  g_csThreadParameter, g_csThreadCode;  
  9. int main()  
  10. {  
  11.     printf("     经典线程同步 关键段\n");  
  12.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  13.   
  14.     //关键段初始化  
  15.     InitializeCriticalSection(&g_csThreadParameter);  
  16.     InitializeCriticalSection(&g_csThreadCode);  
  17.       
  18.     HANDLE  handle[THREAD_NUM];   
  19.     g_nNum = 0;   
  20.     int i = 0;  
  21.     while (i < THREAD_NUM)   
  22.     {  
  23.         EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域  
  24.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);  
  25.         ++i;  
  26.     }  
  27.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  28.   
  29.     DeleteCriticalSection(&g_csThreadCode);  
  30.     DeleteCriticalSection(&g_csThreadParameter);  
  31.     return 0;  
  32. }  
  33. unsigned int __stdcall Fun(void *pPM)  
  34. {  
  35.     int nThreadNum = *(int *)pPM;   
  36.     LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域  
  37.   
  38.     Sleep(50);//some work should to do  
  39.   
  40.     EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域  
  41.     g_nNum++;  
  42.     Sleep(0);//some work should to do  
  43.     printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);  
  44.     LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域  
  45.     return 0;  
  46. }  

运行结果如下图:

可以看出来,各子线程已经可以互斥的访问与输出全局资源了,但主线程与子线程之间的同步还是有点问题。

       这是为什么了?

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

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

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

这一语句。这说明主线程能多次进入这个关键区域!找到主线程和子线程没能同步的原因后,下面就来分析下原因的原因吧^_^

 

先找到关键段CRITICAL_SECTION的定义吧,WinBase.h中被定义成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTIONWinNT.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;

初始化为-1n表示有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_SECTIONlpCriticalSection,

  DWORDdwSpinCount);

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

 

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

函数原型:

DWORDSetCriticalSectionSpinCount(

  LPCRITICAL_SECTIONlpCriticalSection,

  DWORDdwSpinCount);

 

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

 

 

最后总结下关键段:

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

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

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

 

下一篇《秒杀多线程第六篇 经典线程同步 事件Event》将介绍使用事件Event来解决这个经典线程同步问题。

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7442639

如果觉得本文对您有帮助,请点击支持一下,您的支持是我写作最大的动力,谢谢。


 

180
0
主题推荐
多线程 线程 microsoft 高性能 处理器
猜你在找
C++学习之深入理解虚函数--虚函数表解析
手把手实现红黑树
CC++2014年7月华为校招机试真题一
cs硕士妹子找工作经历阿里人搜等互联网
我的2012-分享我的四个项目经验
自己选择的路跪着也要走完
数据库设计中的14个技巧
割绳子的作者你如此歧视无视鄙视中国人这是何苦呢
KMP算法原理与实现精简
【精品课程】三维游戏引擎开发-渲染
【精品课程】iOS开发教程之OC语言
【精品课程】思科认证CCNPv2.0详解第3部分 IPSEC VPN技术
【精品课程】C语言入门教程
【精品课程】J2SE轻松入门第二季
id="ad_frm_0" frameborder="0" scrolling="no" src="http://blog.csdn.net/common/ad.html?t=4&containerId=ad_cen&frmId=ad_frm_0" style="border-width: 0px; overflow: hidden; width: 746px; height: 90px;">
查看评论
52楼  gao880416 2015-03-05 14:53发表 [回复]
楼主想咨询下: EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域 
hand[i] = (HANDLE)_beginthreadex(NULL, 0,Fun,&i,0,NULL);
为什么要设置g_csThreadParameter这个关键段?
Re:  gao880416 2015-03-05 15:38发表 [回复]
回复gao880416:哦,明白了,楼主加上那个临界区主要是为了验证线程同步的。方便后续的添加事件的话题。。明白
51楼  gao880416 2015-03-05 14:32发表 [回复]
看了。。。。楼主的系列文章,比枯燥的核心编程来的易懂点。。多谢分享
50楼  彼岸花 2014-09-26 17:54发表 [回复]
博主,你的意思是不是,while循环里面多次进入临界区的是主线程,但是线程函数里面的临界区,进去的是子线程?
49楼  smalldeveloper 2014-02-26 23:05发表 [回复]
beginthread,笔误
48楼  smalldeveloper 2014-02-26 23:05发表 [回复]
++i 位置没放对吧?
Fun中读取i 的时候可能正好也在进行 ++i 操作

++i 应该放在 begintread之前
47楼  luochengor 2014-01-15 19:12发表 [回复]
楼主,你好,在关键段结构体中第三个参数:LONGRecursionCount; 表示该关键段的拥有线程对此资源获得关键段次数,初为0。我想问一下在什么情况下会让这个参数的计数增加呢?也就是在什么场景下会在本线程调用LeaveCriticalSection之前连续调用EnterCriticalSection两次以上呢?本线程在调用LeaveCriticalSection之前已经获取到访问权了,还需要在调用EnterCriticalSection吗?麻烦楼主给指点一下。
46楼  随心而动随意而行 2013-12-17 19:44发表 [回复]
楼主关于临界区线程拥有权的问题是不是搞混了,线程拥有权真的是指线程A entercritical必须在线程A中leave吗?
楼主可以测试一下,在线程A中enter,在线程B中leave,再在线程B中enter,这样可不可以? 我测试了,这样是可以的,证明临界区关于线程拥有权的问题不是像楼主解释的那样
Re:  MoreWindows 2014-01-03 12:02发表 [回复]
回复u013009575:见38楼
45楼  ponder2011 2013-12-17 14:55发表 [回复]
为什么把线程数改为100,他什么都不输出?
Re:  sky_blue852 2014-04-04 17:19发表 [回复]
回复ponder2011:哈哈 去看 WaitForMultipleObjects的第一参数 个数有个最大值
MAXIMUM_WAIT_OBJECTS 在我win7 64位机器上是64 我当初也写的是100
Re:  scuwax 2014-01-08 21:10发表 [回复]
回复ponder2011:我试了下也是这个情况 是什么原因呢?
Re:  sky_blue852 2014-04-04 17:20发表 [回复]
回复scuwax:看我的回复
Re:  MoreWindows 2013-12-17 14:58发表 [回复]
回复ponder2011:不可能呀,你再试试看。
44楼  amostalong_12345 2013-11-25 12:25发表 [回复]
而且有好多时候都是10,因为很多线程create以后系统没有马上执行线程函数,然后堆到最后,i变成10以后,之前建立线程才调用函数。
Re:  apigcanfly_wfh 2014-12-19 16:22发表 [回复]
回复amostalong_123:想问问,为什么系统不立刻执行子线程?是因为开销问题还是别的,这点不太明白?
Re:  MoreWindows 2013-11-25 20:12发表 [回复]
回复amostalong_123:对,这个是就是主线程与子线程的同步问题,主线程要等子线程取走参数后才能修改。
43楼  amostalong_12345 2013-11-25 12:17发表 [回复]
线程数量定义是10,线程貌似博主意思是0到9,但是i是传的引用给createthread,结果最后一次i++有可能影响到printf时候线程的值吧?所以可能线程资源的值是0-10啰?
42楼  ENIAC初学者 2013-10-16 21:19发表 [回复]
CRITICAL_SECTION g_csThreadParameter,g_csThreadValue;
unsigned _stdcall OutPut(PVOID pvParam)
{

int *num=(int*)pvParam;
int num1=*num;
LeaveCriticalSection(&g_csThreadParameter);
Sleep(50);
EnterCriticalSection(&g_csThreadValue);
//index++;
Sleep(0);
cout<<"num:"<<num1<<endl;
LeaveCriticalSection(&g_csThreadValue);
return 0;
}
void main()
{
// InitializeCriticalSection(&g_csThreadParameter);
// InitializeCriticalSection(&g_csThreadValue);
InitializeCriticalSectionAndSpinCount(&g_csThreadParameter,40000);
InitializeCriticalSectionAndSpinCount(&g_csThreadValue,40000);
HANDLE h[10];
for(int i=0;i!=10;i++)

EnterCriticalSection(&g_csThreadParameter);
h[i]=(HANDLE)_beginthreadex(NULL,0,OutPut,(PVOID)&i,0,NULL);
}
WaitForMultipleObjects(10,h,TRUE,INFINITE);
DeleteCriticalSection(&g_csThreadParameter);
DeleteCriticalSection(&g_csThreadValue);
}
41楼  ENIAC初学者 2013-10-16 21:19发表 [回复]
将旋转锁合并到关键段中,还是没有解决根本问题,运行多次后结果还是有问题,下面是我的代码,楼主有空看下啊!
#define _WIN32_WINNT 0x0500
#include<windows.h>
#include<process.h>
#include<stdio.h>
#include<iostream>
using namespace std;
40楼  wey881117 2013-08-18 18:41发表 [回复]
理解了关键段不能解决同步问题的原因了,谢谢LZ
39楼  ohmygodohyeah 2013-08-11 20:22发表 [回复]
引用“MoreWindows”的评论:回复w2wfyh:这个例子其实是为了证明关键段不能这样使用,子线程中调用的...

ohmygod! 就是这个问题,我已经纠结很久了,始终没想明白为什么主线程中的Level会在子线程中使用。同时也在想若真的这样使用了,那么程序又是怎么执行的!一直 没想明白,LZ能点一下么,谢谢咯!
38楼  风风清清扬扬 2013-08-07 15:12发表 [回复]
CRITICAL_SECTION 我觉得翻译成临界区比较好。
Re:  MoreWindows 2013-08-13 17:43发表 [回复]
回复hujintao2009:《Windows核心编程》上翻译成关键段的,所以我在BLOG中也统一使用这种翻译。
37楼  DarkHorse 2013-06-27 17:45发表 [回复]
MSDN:
If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely. 

如果一个线程调用LeaveCriticalSection对不属于自己的临界区对象操作时会发生错误,将导致原本拥有这个对象的线程调用EnterCriticalSection时无限期的等待
36楼  DarkHorse 2013-06-27 17:05发表 [回复]
推荐一本多线程进程编程的书吧?要经典的
35楼  亚细亚 2013-05-28 17:48发表 [回复]
线程编号显示为什么会有重复值?例如博主给出的运行结果图显示有三个编号为"5",这是怎么回事?
34楼  亚细亚 2013-05-28 16:22发表 [回复]
我在测试这段代码时发现一个问题(我的操作系统是window 2003),就是子线程中LONG LockCount字段的值一直是负值,时而-1,时而-6,时而-10,请问这是怎么回事?
33楼  亚细亚 2013-05-27 14:07发表 [回复]
引用“bigfox2007750219”的评论:加上这一句就更加清楚了:LeaveCriticalSection()在其他线程中也可以调用(而不仅仅...

LeaveCriticalSection(&g_csThreadParameter)在其它线程中可以调用吗?! 如果可以调用另一个线程中的LeaveCriticalSection(&g_csThreadParameter),那在该线程获得所有权之前g_csThreadParameter中参数RecusionCount的数值是多少?
Re:  bigfox2007750219 2013-05-27 20:04发表 [回复]
回复yaxiya:可以调用。
unsigned int __stdcall Fun(void *pPM) 

int nThreadNum = *(int *)pPM; 
LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域 

Sleep(50);//some work should to do 
...
}
上面就是在子线程中调用的,但是是在主线程中EnterCriticalSection(&g_csThreadParameter);

博主不是说了么
第三个参数:LONGRecursionCount; 
表示该关键段的拥有线程对此资源获得关键段次数,初为0。

应该是任何线程都可以调用LeaveCriticalSection,而调用的结果就是使关键段的拥有线程对此资源获得关键段次数减一(没考虑该关键段的拥有线程数为0的情况)
Re:  亚细亚 2013-05-28 09:00发表 [回复]
回复bigfox2007750219:这么说来,就是子线程可以调用LeaveCriticalSection(&g_csThreadParameter)从而使主线程中的RecursionCount减一,对吗?
32楼  亚细亚 2013-05-23 15:56发表 [回复]
引用“bigfox2007750219”的评论:加上这一句就更加清楚了:LeaveCriticalSection()在其他线程中也可以调用(而不仅仅...

调试结果:拥有他的线程多次EnterCriticalSection(),而可以不LeaveCriticalSection();当执行另一个线程时,可以切换过来执教执行前一个线程中的LeaveCriticalSection();这俩个线程没有执行完就相互交叉着往下执行;
还有一个疑问就是RTL_CRITICAL_SECTION中的参数LockCount搞不明白是什么意思?这个参数初始化是-1,经过多次调试查看:主线程中该参数要么就是-1,要么就是-2;而子线程中该参数值有时候变化比较大,有时候还有-14呢?
真不知道这个参数在允许过程中到底表示什么?
31楼  亚细亚 2013-05-22 16:29发表 [回复]
[cpp]  view plain copy
  1. "LeaveCriticalSection(&g_csThreadParameter);"这句为什么不放在"++i;"后面;反而要放在Fun方法里,不知到是什么意思?能否解释下?谢谢!  
  2.   while (i < THREAD_NUM)     
  3.         {    
  4.              //printf("线程ID号为%4d\n", GetCurrentThreadId());    
  5.             EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域    
  6.             handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);    
  7.             ++i;    
  8. LeaveCriticalSection(&g_csThreadParameter);  
  9.         }    
30楼  bigfox2007750219 2013-05-22 13:41发表 [回复]
加上这一句就更加清楚了:LeaveCriticalSection()在其他线程中也可以调用(而不仅仅是拥有它的线程),也可以使当前线程拥有者的进入的次数减一。
Re:  随心而动随意而行 2013-12-17 19:52发表 [回复]
引用“bigfox2007750219”的评论:加上这一句就更加清楚了:LeaveCriticalSection()在其他线程中也可以调用(而不仅仅...

同意,亲自测试,这样是可以的,所以楼主所说的线程拥有权的问题是不是存在问题??
29楼  jianxianbai 2013-04-17 14:41发表 [回复]
楼主,能请教一下旋转锁的具体用法和效果吗?或者再增加一篇?非常感谢
Re:  MoreWindows 2013-04-18 13:03发表 [回复]
回复jianxianbai:可以,不过现在正在改论文,快答辩了。答辩后有空就来写。
Re:  亚细亚 2013-05-23 16:19发表 [回复]
回复MoreWindows:期待博主写旋转锁的具体用法;看了博主的文章真实受益匪浅;
Re:  jianxianbai 2013-04-19 13:06发表 [回复]
回复MoreWindows:谢谢,楼主还是学生?厉害啊!
28楼  bycn 2013-04-06 19:36发表 [回复]
测试了下,线程A调用EnterCriticalSection,线程B调用LeaveCriticalSection是没问题的。
两个线程的一次性有序输出没有问题,它可以作为不同线程间的信号使用。要求多次有序输出才会因为单个线程支持多次EnterCriticalSection而出顺序错乱。
27楼  lindeshi 2013-02-26 16:23发表 [回复]
LZ对临界区的讲解和理解都很深刻,膜拜了。
但是有一点很容易照成误解应该强调一下,以下
摘自《windows并发编程指南》第六章,P160:
离开一个未拥有的临界区
如果试图离开一个并不是有当前线程拥有的临界区,那么将是一个非常严重的错误。这意味着

在程序存在一个错误,并且在发生这种情况时不会立即出现表示错误的信息。既不会返回错误

码也不会抛出异常。外表上看来,所有的事情都是进展顺利,但却留下了一个定时炸弹。
如果在LeaveCriticalSection时没有拥有临界区,那么接下来所有对EnterCriticalSection的

调用都将被阻塞。这实际上使得所有在随后试图使用这个临界区的线程都将形成死锁(这里是

说,调用Leave的那个线程如果接下来再调用Enter的话这个线程就会永远阻塞,而且在它调用

了Enter之后,所有使用临界区的线程都讲话阻塞包括之前拥有这个临界区的线程。但是,在

调用了Leave之后和调用Enter之前,拥有临界区的那个线程还是可以Enter进去的)。如果未拥

有临界区的线程在尝试执行离开临界区操作时,这个临界区正被另一个线程所拥有,那么当前

所有者仍然可以递归地获取和释放这个临界区。但当所有者退出这个临界区时,它将被永久性

地破坏:随后将发生于前面相同的行为,即系统中的任何线程在随后的调用Enter时都将被永

远地阻塞。
Re:  zhang_g_y 2014-01-03 11:36发表 [回复]
回复lindeshi:LZ讲得很详细,通俗易懂,初学也能看得很明白。但层主这点补充的很有用啊,LZ的例子针对这点并不是很典型,差点漏过去了。。
26楼  Aerchi 2013-01-11 21:20发表 [回复]
just for study
25楼  miao6664659 2013-01-10 21:30发表 [回复]
楼主应该强调一下:关键段有“线程所有权的”,所以关键段无法应用于[主调线程]与其他线程的同步,但是可以用于【非主调线程】之外的其余线程的同步。
Re:  MoreWindows 2013-01-11 14:31发表 [回复]
回复miao6664659:这种说法不对,你可以试下只用关键段完成下面这个任务。
二个子线程交替输出1, 2, 3, 4, 5......
注意,必须交替输出。
Re:  miao6664659 2013-01-11 16:35发表 [回复]
int g_t=0;
HANDLE g_hThreadEvent1;
CRITICAL_SECTION g_csThreadCode1;
HANDLE event0;
HANDLE event1;
unsigned int __stdcall testFun1(void* pPM)
{
int nThreadID=*(int *)pPM;
SetEvent(g_hThreadEvent1);
Sleep(50);
for(int i=0;i<10;++i)
{
if(nThreadID==0)
{
WaitForSingleObject(event0,INFINITE);
}
else
WaitForSingleObject(event1,INFINITE);
EnterCriticalSection(&g_csThreadCode1);
if(nThreadID==0)
{
//阻塞当前线程 激活另外一个线程
ResetEvent(event0);
SetEvent(event1);
}
else
{
ResetEvent(event1);
SetEvent(event0);
}
cout<<"ID:"<<nThreadID<<"---"<<"值为:"<<g_t++<<endl;
LeaveCriticalSection(&g_csThreadCode1);
}
return 0;
}
int i=0;
while(i<2)
{
handle[i]=(HANDLE)_beginthreadex(NULL,0,testFun1,&i,0,NULL);
WaitForSingleObject(g_hThreadEvent1,INFINITE);
i++;
}
WaitForMultipleObjects(2,handle,TRUE,INFINITE);
贴一下测试代码
Re:  miao6664659 2013-01-11 16:32发表 [回复]
回复MoreWindows:嗯 学习了!
24楼  linrulei11 2012-10-24 09:22发表 [回复]
想请问一下,为什么输出的线程ID会有重复的?谢谢
Re:  Unix_Architect 2013-01-01 14:51发表 [回复]
回复linrulei11:应该是一只输出9对吧?
因为他传递的是地址,传递的是&i,当到最后的时候i已经是9了,并且该地址传递进去了。
所以即使之前i=1,但是由于最后传递的时候是9,并且传递的是地址,并非值,所以一直是9.
Re:  MoreWindows 2012-10-24 10:57发表 [回复]
回复linrulei11:这是因为关键段不能用于线程同步。
Re:  Unix_Architect 2013-01-01 14:52发表 [回复]
回复MoreWindows:标号不一样,就代表关键代码段不能同步?

标号不一样是因为你使用了地址传递!既然不能同步,为什么全局的值还是递增的呢?

另外,子线程和主线程同步不同步有什么意义呢?线程本来就是不同步的,所谓线程同步是针对修改变量的顺序要做到同步。只要被修改的变量被同步了,那么线程就是同步的。
Re:  passball 2012-11-17 15:26发表 [回复]
回复MoreWindows:不是很明白。。。即使不能同步,但是怎么能输出循环过的值呢?比如i=9,后又怎么会再输出6,8呢?是临界区有记录吗?就比如房卡会记住门牌号,但怎么会记往是谁在使用呢?
Re:  MoreWindows 2012-11-19 11:58发表 [回复]
回复passball:“线程所有权”只要记录下线程的ID号就可以了,这个ID号是整个系统唯一的。
Re:  passball 2012-11-20 15:45发表 [回复]
回复MoreWindows:线程ID应该是唯一的吧?也就是说虽然是同一个线程函数,但每次循环进入一次函数就会分配唯的的ID,对吗?
那么应该是每一个i都要对应一个线程ID啊,即使有先后,所有的i也应该都输出啊,为什么有的i没有了呢?
另外,同一个线程可以重复进入临界区,以结构体中的计数为准,但是多个enter()可以用一个leave()就结束了吧?
Re:  MoreWindows 2012-11-20 17:06发表 [回复]
回复passball:你要看下《操作系统》(汤子瀛版),不然很多知识点会搞混淆的。
对于“同一个线程可以重复进入临界区,以结构体中的计数为准,但是多个enter()可以用一个leave()就结束了吧?”
这个不能,因此关键段不但会记录线程ID号,还会记录线程占用关键段次数,只有占用次数为0后,该关键段才能被其它线程占用。
Re:  亚细亚 2013-05-23 16:39发表 [回复]
引用“MoreWindows”的评论:回复passball:你要看下《操作系统》(汤子瀛版),不然很多知识点会搞...

“同一个线程可以重复进入临界区,以结构体中的计数为准,但是多个enter()可以用一个leave()就结束了吧?”
这句话如果出一个判断题的话,估计大部分人都会认为同一个线程不能重复进入临界区品;测试了你的代码,受益匪浅啊;THANKS!
Re:  linrulei11 2012-10-29 16:21发表 [回复]
回复MoreWindows:谢谢回复,我已经懂了!收获很多啊!
23楼  眸中怀 2012-08-27 16:00发表 [回复]
楼主,我想问下,为什么要对子线程序号进行关键段保护呢,把第一个段区不要行不行呢?
22楼  小强_加油 2012-08-15 16:36发表 [回复]
楼主,我按照你的程序去写但是,执行完一次线程调用后,就一直无法在向下执行了。但是程序也没问题,就是一直在等待。这是什么问题啊
Re:  小强_加油 2012-08-16 11:46发表 [回复]
引用“MoreWindows”的评论:回复aini201:发代码给我看下。

unsigned int __stdcall Fun(void *Pama)
{

int nThreadNum=*(int *)Pama;
LeaveCriticalSection(&g_csThreadPrama);

EnterCriticalSection(&g_csThreadCode);
Sleep(100);

_g_count++;
Sleep(0);
printf("线程编号%d,全局变量为%d\n",nThreadNum,_g_count);
LeaveCriticalSection(&g_csThreadPrama);
return 0;
}
Re:  MoreWindows 2012-08-19 19:25发表 [回复]
回复aini201:你代码中的
printf("线程编号%d,全局变量为%d\n",nThreadNum,_g_count);
LeaveCriticalSection(&g_csThreadPrama);
应该要写成
LeaveCriticalSection(&g_csThreadCode);
Re:  MoreWindows 2012-08-16 10:33发表 [回复]
回复aini201:发代码给我看下。
Re:  小强_加油 2012-08-16 11:46发表 [回复]
回复MoreWindows:int main()
{

_g_count=0;

InitializeCriticalSection(&g_csThreadPrama);
InitializeCriticalSection(&g_csThreadCode);
if(InitializeCriticalSectionAndSpinCount(&g_csThreadPrama,4000))
{
printf("临界区初始化成功\n");
}
else
{
return -1;
}
if(InitializeCriticalSectionAndSpinCount(&g_csThreadCode,4000))
{
printf("临界区初始化成功\n");
}
else
{
return -1;
}
HANDLE handle[THREAD_NUM];
int i=0;

while (i<THREAD_NUM)
{

EnterCriticalSection(&g_csThreadPrama);

// handle[i]=(HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
handle[i]=(HANDLE)_beginthreadex(NULL,0,Fun,&i,0,NULL);
i++;
}
WaitForMultipleObjects(THREAD_NUM,handle,true,INFINITE);

DeleteCriticalSection(&g_csThreadCode);
//(HANDLE)_endthreadex(handle);
DeleteCriticalSection(&g_csThreadPrama);

return 0;
}
每次运行的时候,打印一次后就无法向下运行了,不过程序也没死掉。是不是因为临界区一直被占用未能释放啊。谢谢楼主了
Re:  MoreWindows 2012-08-19 19:26发表 [回复]
回复aini201:是的,你代码中的
printf("线程编号%d,全局变量为%d\n",nThreadNum,_g_count);
LeaveCriticalSection(&g_csThreadPrama);
应该要写成
LeaveCriticalSection(&g_csThreadCode);
Re:  小强_加油 2012-08-19 21:15发表 [回复]
回复MoreWindows:是的,谢谢楼主
21楼  jq8chu 2012-08-12 20:01发表 [回复]
一直在关注lz的文章,关于关键代码段这个我自己写了一下,代码和lz的一样,只是将#include <stdio.h>改为c++的#include<iostream> + using namespace std; 线程编号部分就不存在同步问题了,不知道lz有没有主要到这个问题,估计命名空间std应该和关键代码段有点联系吧
Re:  MoreWindows 2012-08-12 20:47发表 [回复]
回复jq8chu:命名空间和线程同步没什么关系吧,输出的线程编号偶尔也会正确,但多运行几次就能发现问题的。
20楼  wangchendh 2012-08-09 18:19发表 [回复]
楼主强大..学习中。。
19楼  liuxy1987 2012-07-28 18:41发表 [回复]
lz 这种野路子好用不。。。 
for(int i=0; i<TOTAL_THREAD; i++){
while(cs.RecursionCount >= 1);
EnterCriticalSection(&cs);
hThreadList[i] = CreateThread(NULL, 0, ThreadGo, &i, 0, NULL);
}
Re:  MoreWindows 2012-07-28 20:20发表 [回复]
回复liuxy1987:用来试验一下可以,正规场合不要这样做,不然微软要是改变关键段的底层实现你就麻烦了。
Re:  liuxy1987 2012-07-29 10:27发表 [回复]
恩,先把后续的篇章看完,把经典的方法都学会先, lz厉害回复MoreWindows:
18楼  MoreFocus 2012-07-13 14:22发表 [回复]
请教楼主一个问题。一个没有获得关键段所有权的线程调用EnterCriticalSection会导致等待,那同样的情况调用LeaveCriticalSection会不会也等待。如果是的话,那么为什么该段代码中主线程里并没有调用 LeaveCriticalSectio(&g_csThreadParameter);而子线程依然可以顺利执行。
17楼  iamzhaiwei 2012-06-28 10:59发表 [回复]
互斥、异步、同步是有区别的
16楼  Galaxy_Li 2012-06-22 09:49发表 [回复]
博主使用了Sleep(0),这个有什么特殊含义吗?
Re:  lichaoyin 2013-02-25 17:32发表 [回复]
回复Galaxy_Li:MSDN上面的解释是“If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready. ” 意思应该是说,该线程放弃当前分得时间片的剩余执行时间,从“进行态”直接进入“就绪态”,一旦再次获得时间片,可立即执行。
15楼  grassinwind 2012-06-15 19:46发表 [回复]
引用“w2wfyh”的评论:您好! 文中说:关键段是有“线程所有权”概念的。一旦拥有线程所有权的线程调用LeaveCritica...

既然这种用法是错误的 那接下来的讨论 线程所有权的问题还有意义吗
Re:  MoreWindows 2012-06-16 10:53发表 [回复]
回复grassinwind:试验一下总会印象深刻些呀。
14楼  wangbo56916860 2012-05-29 17:44发表 [回复]
成员没有跑过,不过有看过上一章节,说要求子线程当中的i值顺序增长输出。以目前的code来看,运行结果的顺序可能是随即的,但是i值是不同的。

unsigned int __stdcall Fun(void *pPM) 

int nThreadNum = *(int *)pPM; 


Sleep(50);//some work should to do 

EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域 
g_nNum++; 
Sleep(0);//some work should to do 
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); 
LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域 
LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域 
return 0; 

不知道是否要在子线程运行结束以后才离开关键字段。

unsigned int __stdcall Fun(void *pPM) 

int nThreadNum = *(int *)pPM; 


Sleep(50);//some work should to do 

EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域 
LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域 
g_nNum++; 
Sleep(0);//some work should to do 
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); 

LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域 
return 0; 
}
Re:  wangbo56916860 2012-05-29 17:51发表 [回复]
错了!~ Critical_Section不能用于同步,即使调整code的位置也不能有序递增的输出i的值
Re:  MoreWindows 2012-05-29 18:01发表 [回复]
回复wangbo56916860:呵呵,肯定啦,它有“线程所有权”的。
13楼  eagleatustb 2012-05-22 15:22发表 [回复]
不错,从API和代码角度解析了此同步方法,我觉得CRITICAL_SECTION不能跨进程使用以及原因,还有多次进入关键段的情况也应该说明一下。
Re:  MoreWindows 2012-05-22 16:27发表 [回复]
回复eagleatustb:CRITICAL_SECTION不能跨进程使用,我觉得这个因为二点吧:
1。没有这方面的接口,如事件的OpenEvent()。
2。本身不是内核对象。
还望高手指点。
Re:  MoreWindows 2012-05-22 16:25发表 [回复]
回复eagleatustb:多次进入关键段会导致RecursionCount的增加。文章中提到了“第三个参数:LONG RecursionCount; 
表示该关键段的拥有线程对此资源获得关键段次数,初为0。”
12楼  w2wfyh 2012-05-20 11:28发表 [回复]
您好! 文中说:关键段是有“线程所有权”概念的。一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0时,系统会自动更新关键段并将等待中的线程换回可调度状态。
是不是说主线程拥有 g_csThreadParameter的线程所有权?可是为什么是在子线程中调用的LeaveCriticalSection()?
请解惑,多谢!~~
Re:  MoreWindows 2012-05-21 10:26发表 [回复]
回复w2wfyh:这个例子其实是为了证明关键段不能这样使用,子线程中调用的LeaveCriticalSection()会执行失败。从而导致主线程和子线程之间不能同步。
Re:  w2wfyh 2012-05-22 10:39发表 [回复]
回复MoreWindows:是不是在主线程中初始化和删除关键段,在每个子线程中执行EnterCriticalSection()和LeaveCriticalSection(&g_csThreadCode);也就是说本文中的g_csThreadParameter不能起到关键段应有的作用,而 g_csThreadCode才能起到关键段的作用?
Re:  MoreWindows 2012-05-22 14:52发表 [回复]
回复w2wfyh:对的,EnterCriticalSection和LeaveCriticalSection要成对的使用,可以参见《秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量》。
11楼  lixiaojun9688 2012-05-20 11:20发表 [回复]
赞一个,很不错的讲解,我对这个为啥有“线程所有权”而就不能用于线程同步还是有点不太理解,希望楼主能给点详细的解释
Re:  MoreWindows 2012-05-21 10:36发表 [回复]
回复lixiaojun9688:我个人理解是:同步是指线程执行的次序问题,如生产者和消费者(缓冲区个数为1),生产者要等消费者取走产品即缓冲区为空,消费者要等生产者投放产品即缓冲区非空。这对二个线程都是一种制约。而关键段的“线程拥有权”特性决定了拥有关键段的线程可以不受限制的运行,从而违反了二个线程都要受制约的要求。
Re:  Galaxy_Li 2012-06-22 13:38发表 [回复]
回复MoreWindows:我觉得,有些同步问题用关键段来解决是有问题的,而有些同步问题是可以用关键段来解决的。
Re:  Galaxy_Li 2012-06-22 10:49发表 [回复]
关键段不可以用来解决生产者消费者这种同步问题吗?回复MoreWindows:
Re:  MoreWindows 2012-08-12 20:45发表 [回复]
回复Galaxy_Li:关键段不能跨进程来使用,所以不能用于生产者消费者问题。
Re:  aa25693592 2013-01-02 21:09发表 [回复]
回复MoreWindows:那关键段能用于生产者线程的互斥吗?或者是消费者的互斥?
10楼  fpgzs2 2012-05-18 10:52发表 [回复]
CRITICAL_SECTION通常说法是“临界区”吧,“关键段”这翻译感觉有点拐扭!
Re:  super_admi 2013-08-26 17:55发表 [回复]
回复fpgzs2:俺也是觉得,“临界区”比“关键段”用得更普遍……
Re:  MoreWindows 2012-05-18 14:40发表 [回复]
回复fpgzs2:《Windows核心编程》就译成关键段的。
9楼  飞翔的井蛙 2012-04-19 21:42发表 [回复]
必须要攒一个,过两天面试,希望能有帮组,当然对于以后的工作还有很多帮助的,谢谢lz
Re:  MoreWindows 2012-04-20 10:17发表 [回复]
回复colorfulchg:呵呵,加油。
8楼  cpu_12593 2012-04-15 08:30发表 [回复]
旋转锁是什么意思呀?那个设置旋转次数的参数是说在固定的时间内旋转的次数吗?
7楼  zhenzigis 2012-04-12 15:12发表 [回复]
引用“pangpangguai”的评论:嗯,确实通俗易懂,LZ辛苦了!

同意。
6楼  lanzhengpeng2 2012-04-12 08:50发表 [回复]
关键段是有“线程所有权”概念的
这个很重要.
Re:  MoreWindows 2012-04-12 19:44发表 [回复]
回复lanzhengpeng2:正是因为这个“线程所有权”,所以关键段不能用于线程同步。
Re:  lichaoyin 2013-02-25 17:17发表 [回复]
回复MoreWindows:"拥有线程所有权"是关键段可以用于线程互斥的前提条件,但这并不能说明其不能用于线程同步。虽然只能在“拥有者”线程中Enter,却可以在任何线程中对该关键段进行Leave操作,这也就为线程同步提供了条件。

我们只需要在main方法中的语句
EnterCriticalSection(&g_csThreadParameter);
后面添加:
while (g_csThreadParameter.RecursionCount>1)
{
Sleep(0);
}
通过限制对g_csThreadParameter的拥有次数来实现对主线程的约束,即可实现同步。

另外,楼主的“房卡”比喻很形象,我觉得可以说的更完善一些,拥有房卡的人(即主人)可以多次进出房间,主人也可以带客人进房间,房门可以由主人或客人来关闭。
Re:  nocarefree 2014-07-26 11:22发表 [回复]
回复lichaoyin:确实是
Re:  追风筝的小男孩 2014-05-06 23:56发表 [回复]
回复lichaoyin:正解!
Re:  zhuyf87 2013-06-15 09:37发表 [回复]
回复lichaoyin:同意这个观点。
Re:  xiaolomg 2012-12-24 21:47发表 [回复]
回复MoreWindows:lz能否解释下线程同步的应用,为什么不能做“线程同步”?
5楼  yufeisunxiang 2012-04-11 19:19发表 [回复]
不懂
4楼  yufeisunxiang 2012-04-11 19:18发表 [回复]
3楼  jianshiku 2012-04-11 10:05发表 [回复] [引用] [举报]
2楼  pangpangguai 2012-04-11 09:31发表 [回复]
嗯,确实通俗易懂,LZ辛苦了!
1楼  zlevel 2012-04-11 09:14发表 [回复]
不错,深入浅出,通俗易懂。赞LZ一个。
Re:  MoreWindows 2012-04-11 09:22发表 [回复]
回复zlevel:呵呵,谢谢支持。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多线程编程中,线程同步是非常重要的。线程同步是指在多个线程并发访问共享资源时,为了避免出现数据不一致或其他问题,需要对线程的执行进行协调和控制。常见的线程同步方式包括锁、信号量、条件变量等。 在 Python 中,线程同步可以通过 threading 模块中的 Lock 类来实现。Lock 类提供了 acquire() 和 release() 方法,用于控制资源的访问。 使用 Lock 类的基本流程如下: 1. 创建 Lock 对象。 2. 在需要访问共享资源的代码块前调用 acquire() 方法获取锁,阻塞其他线程对该资源的访问。 3. 在访问共享资源的代码块后调用 release() 方法释放锁,允许其他线程对该资源的访问。 下面是一个使用 Lock 类实现线程同步的示例: ```python import threading # 共享资源 count = 0 # 创建 Lock 对象 lock = threading.Lock() def add(): global count for i in range(100000): # 获取锁 lock.acquire() count += 1 # 释放锁 lock.release() def sub(): global count for i in range(100000): # 获取锁 lock.acquire() count -= 1 # 释放锁 lock.release() # 创建两个线程 t1 = threading.Thread(target=add) t2 = threading.Thread(target=sub) # 启动线程 t1.start() t2.start() # 等待线程执行结束 t1.join() t2.join() print(count) ``` 在上面的示例中,我们创建了一个共享资源 count,然后分别创建了两个线程对该资源进行加和减操作。在访问共享资源的代码块前,我们使用 acquire() 方法获取锁,阻塞其他线程对该资源的访问;在访问共享资源的代码块后,我们使用 release() 方法释放锁,允许其他线程对该资源的访问。 需要注意的是,获取锁和释放锁的代码必须成对出现,否则会出现死锁等问题。此外,在使用锁进行线程同步时,应尽量避免持锁时间过长,以免影响程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值