2023年6月22日发(作者:)
C++:线程操作之CRITICAL_SECTION⽤法的介绍和例⼦理解CRITICAL_SECTION介绍CRITICAL_SECTION是每个线程中访问临界资源的那段代码,不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进⾏访问;每个线程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是⼀次仅允许⼀个线程使⽤的共享资源)。每次只准许⼀个线程进⼊临界区,进⼊后不允许其他线程进⼊。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进⾏访问。线程进⼊临界区的调度原则是:①如果有若⼲线程要求进⼊空闲的临界区,⼀次仅允许⼀个线程进⼊。②任何时候,处于临界区内的线程不可多于⼀个。如已有线程进⼊⾃⼰的临界区,则其它所有试图进⼊临界区的线程必须等待。③进⼊临界区的线程要在有限时间内退出,以便其它线程能及时进⼊⾃⼰的临界区。④如果线程不能进⼊⾃⼰的临界区,则应让出CPU,避免线程出现“忙等”现象。如果有多个线程试图同时访问临界区,那么在有⼀个线程进⼊后其他所有试图访问此临界区的线程将被挂起,并⼀直持续到进⼊临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到⽤原⼦⽅式操作共享资源的⽬的。临界区在使⽤时以CRITICAL_SECTION结构对象保护共享资源,并分别⽤**EnterCriticalSection()和LeaveCriticalSection()**函数去标识和释放⼀个临界区。所⽤到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使⽤,⽽且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作⽤,共享资源依然有被破坏的可能。简单来说,EnterCriticalSection没有给资源加锁,只是给线程加了锁,对于加了同⼀种锁的线程,只能依次执⾏,不许同步执⾏,其实,CRITICAL_SECTION是不能够“锁定”资源的,它能够完成的功能,是同步不同线程的代码段。简单说,当⼀个线程执⾏了EnterCritialSection之后,临界区结构对象cs⾥⾯的信息便被修改,以指明哪⼀个线程占⽤了它。⽽此时,并没有任何资源被“锁定”。不管什么资源,其它线程都还是可以访问的(当然,执⾏的结果可能是错误的)。只不过,在这个线程尚未执⾏LeaveCriticalSection之前,其它线程碰到EnterCritialSection语句的话,就会处于等待状态,相当于线程被挂起了。 这种情况下,就起到了保护共享资源的作⽤。也正由于CRITICAL_SECTION是这样发挥作⽤的,所以,必须把每⼀个线程中访问共享资源的语句都放在EnterCritialSection和LeaveCriticalSection之间。这是初学者很容易忽略的地⽅。什么时候可以⽤到:线程不多时,全部为他们加同⼀种锁,使他们依次执⾏或者加两种锁,两个⽅式同时执⾏但是对于不加锁或者加了不同锁的线程,可以同步执⾏:如果⽤到两个CRITICAL_SECTION,⽐如说:第⼀个线程已经执⾏了EnterCriticalSection(&cs)并且还没有执⾏LeaveCriticalSection(&cs),这时另⼀个线程想要执⾏EnterCriticalSection(&cs2),这种情况是可以的(除⾮cs2已经被第三个线程抢先占⽤了)。这也就是多个CRITICAL_SECTION实现同步的思想。实例编辑1⽐如说我们定义了⼀个共享资源dwTime[100],两个线程ThreadFuncA和ThreadFuncB都对它进⾏读写操作。当我们想要保证dwTime[100]的操作完整性,即不希望写到⼀半的数据被另⼀个线程读取,那么⽤CRITICAL_SECTION来进⾏线程同步如下:第⼀个线程函数:DWORD WINAPI ThreadFuncA(LPVOID lp){ EnterCriticalSection(&cs); ... //
操作dwTime ... LeaveCriticalSection(&cs); return 0;}dwTime并没有和任何东西对应,它仍然是任何其它线程都可以访问的。不要错误地以为,此时cs对dwTime进⾏了锁定操作,dwTime处于cs的保护之中。⼀个“⾃然⽽然”的想法就是——cs和dwTime⼀⼀对应上了。这么想,就⼤错特错了。如果你像如下的⽅式来写第⼆个线程,那么就会有问题:DWORD WINAPI ThreadFuncB(LPVOID lp){ ... //
操作dwTime ... return 0;}当线程ThreadFuncA执⾏了EnterCriticalSection(&cs),并开始操作dwTime[100]的时候,线程ThreadFuncB可能随时醒过来,也开始操作dwTime[100],这样,dwTime[100]中的数据就被破坏了。为了让 CRITICAL_SECTION发挥作⽤,我们必须在访问dwTime的任何⼀个地⽅都加上 EnterCriticalSection(&cs)和LeaveCriticalSection(&cs)语句。所以,必须按照下⾯的⽅式来写第⼆个线程函数:DWORD WINAPI ThreadFuncB(LPVOID lp){ EnterCriticalSection(&cs); ... //
操作dwTime ... LeaveCriticalSection(&cs); return 0;}这样,当线程ThreadFuncB醒过来时,它遇到的第⼀个语句是EnterCriticalSection(&cs),这个语句将对cs变量进⾏访问。如果这个时候第⼀个线程仍然在操作dwTime[100],cs变量中包含的值将告诉第⼆个线程,已有其它线程占⽤了cs。因此,第⼆个线程的EnterCriticalSection(&cs)语句将不会返回,⽽处于挂起等待状态。直到第⼀个线程执⾏了 LeaveCriticalSection(&cs),第⼆个线程的EnterCriticalSection(&cs)语句才会返回,并且继续执⾏下⾯的操作。说明: 这个过程实际上是通过限制有且只有⼀个函数进⼊CriticalSection变量来实现代码段同步的。简单地说,对于同⼀个CRITICAL_SECTION,当⼀个线程执⾏了EnterCriticalSection⽽没有执⾏ LeaveCriticalSection的时候,其它任何⼀个线程都⽆法完全执⾏EnterCriticalSection⽽不得不处于等待状态。再次强调⼀次,没有任何资源被“锁定”,CRITICAL_SECTION这个东东不是针对于资源的,⽽是针对于不同线程间的代码段的!我们能够⽤它来进⾏所谓资源的“锁定”,其实是因为我们在任何访问共享资源的地⽅都加⼊了EnterCriticalSection和 LeaveCriticalSection语句,使得同⼀时间只能够有⼀个线程的代码段访问到该共享资源⽽已(其它想访问该资源的代如果是两个CRITICAL_SECTION,就以此类推。码段不得不等待)。如果是两个CRITICAL_SECTION,就以此类推。⽰例再举个极端的例⼦,可以帮助你理解CRITICAL_SECTION这个东东:第⼀个线程函数:DWORD WINAPI ThreadFuncA(LPVOID lp){ EnterCriticalSection(&cs); for(int i=0;i <1000;i++) Sleep(1000); LeaveCriticalSection(&cs); return 0;}第⼆个线程函数:DWORD WINAPI ThreadFuncB(LPVOID lp){ EnterCriticalSection(&cs); index=2; LeaveCriticalSection(&cs); return 0;}
这种情况下,第⼀个线程中间总共Sleep了1000秒钟!它显然没有对任何资源进⾏什么“有意识”的保护;⽽第⼆个线程是要访问资源index的,但是由于第⼀个线程占⽤了cs,⼀直没有Leave,⽽导致第⼆个线程不得不等上1000秒钟……你会看到第⼆个线程在1000秒钟之后开始执⾏index=2这个语句。也就是说,CRITICAL_SECTION其实并不理会你关⼼的具体共享资源,它只关系你是否占⽤了cs实例编辑2下⾯通过⼀段代码展⽰了临界区在保护多线程访问的共享资源中的作⽤。通过两个线程来分别对全局变量g_cArray[10]进⾏写⼊操作,⽤临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进⾏初始化。为了使实验效果更加明显,体现出临界区的作⽤,在线程函数对共享资源g_cArray[10]的写⼊时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增⼤。如果不使⽤临界区对其进⾏保护,则共享资源数据将被破坏(参见图1(a)所⽰计算结果),⽽使⽤临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所⽰计算结果)。代码实现清单附下://
临界区结构对象CRITICAL_SECTION g_cs;//
共享资源char g_cArray[10];UINT ThreadProc10(LPVOID pParam){//
进⼊临界区EnterCriticalSection(&g_cs);//
对共享资源进⾏写⼊操作for (int i = 0; i < 10; i++){g_cArray = a;Sleep(1);}//
离开临界区LeaveCriticalSection(&g_cs);return 0;}UINT ThreadProc11(LPVOID pParam){//
进⼊临界区EnterCriticalSection(&g_cs);//
对共享资源进⾏写⼊操作for (int i = 0; i < 10; i++){g_cArray[10 - i - 1] = b;Sleep(1);}//
离开临界区LeaveCriticalSection(&g_cs);return 0;}……void CSample08View::OnCriticalSection(){//
初始化临界区InitializeCriticalSection(&g_cs);//
启动线程AfxBeginThread(ThreadProc10, NULL);AfxBeginThread(ThreadProc11, NULL);//
等待计算完毕Sleep(300);//
报告计算结果CString sResult = CString(g_cArray);AfxMessageBox(sResult);}下⾯看代码(全部加同⼀种锁):UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter){CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;int i, j;EnterCriticalSection(&pthis->g_cs);for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + "threadOne: %drn", pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}LeaveCriticalSection(&pthis->g_cs);return 0;}UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter){CThreadLockTestOneDlg *pthis = (CThreadLockTestOneDlg*)lpParameter;int i, j;EnterCriticalSection(&pthis->g_cs);for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + "nthreadSecond: %drn", pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}LeaveCriticalSection(&pthis->g_cs);return 0;}此时开启两个线程的话InitializeCriticalSection(&g_cs);str = "";value = 0;hThread1 = AfxBeginThread((AFX_THREADPROC)fThread1, this);hThread2 = AfxBeginThread((AFX_THREADPROC)fThread2, this);执⾏结果为:(优先执⾏先加锁的)threadOne: 1threadOne: 2threadOne: 3threadOne: 4threadOne: 5threadOne: 6threadOne: 7threadOne: 8threadOne: 8threadOne: 9threadOne: 10threadSecond: 11threadSecond: 12threadSecond: 13threadSecond: 14threadSecond: 15threadSecond: 16threadSecond: 17threadSecond: 18threadSecond: 19threadSecond: 20⽽第⼆段代码(⼀个加锁,⼀个不加锁):UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter){CThreadLockTestOneDlg
pthis = (CThreadLockTestOneDlg)lpParameter;int i, j;EnterCriticalSection(&pthis->g_cs);for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + “threadOne: %drn”, pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}LeaveCriticalSection(&pthis->g_cs);return 0;}UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter){CThreadLockTestOneDlg
pthis = (CThreadLockTestOneDlg)lpParameter;int i, j;for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + “nthreadSecond: %drn”, pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}return 0;}执⾏结果为(都可以访问资源,同步执⾏):threadOne: 1threadSecond: 2threadOne: 3threadSecond: 4threadOne: 5threadSecond: 6threadOne: 7threadSecond: 8threadOne: 9threadSecond: 10threadOne: 11threadSecond: 12threadOne: 13threadSecond: 14threadOne: 15threadOne: 15threadSecond: 16threadOne: 17threadSecond: 18threadOne: 19threadSecond: 20第三段代码(加了不同的锁):UINT CThreadLockTestOneDlg::fThread1(LPVOID lpParameter){CThreadLockTestOneDlg
pthis = (CThreadLockTestOneDlg)lpParameter;int i, j;EnterCriticalSection(&pthis->g_cs);for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + “threadOne: %drn”, pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}LeaveCriticalSection(&pthis->g_cs);return 0;}UINT CThreadLockTestOneDlg::fThread2(LPVOID lpParameter){CThreadLockTestOneDlg
pthis = (CThreadLockTestOneDlg)lpParameter;int i, j;EnterCriticalSection(&pthis->g_cs2);for (i = 1; i<=10; i++){pthis->value++;pthis->(pthis->str + “nthreadSecond: %drn”, pthis->value);pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);}LeaveCriticalSection(&pthis->g_cs2);return 0;}执⾏结果(同步执⾏):threadOne: 1threadSecond: 2threadOne: 3threadSecond: 4threadOne: 5threadSecond: 6threadOne: 7threadSecond: 8threadOne: 9threadSecond: 10threadOne: 11threadSecond: 12threadOne: 13threadSecond: 14threadOne: 15threadSecond: 16threadOne: 17threadSecond: 18threadOne: 19threadSecond: 20threadSecond: 20在使⽤临界区时,⼀般不允许其运⾏时间过长,只要进⼊临界区的线程还没有离开,其他所有试图进⼊此临界区的线程都会被挂起⽽进⼊到等待状态,并会在⼀定程度上影响程序的运⾏性能。尤其需要注意的是不要将等待⽤户输⼊或是其他⼀些外界⼲预的操作包含到临界区。如果进⼊了临界区却⼀直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执⾏了EnterCriticalSection()语句进⼊临界区后⽆论发⽣什么,必须确保与之匹配的LeaveCriticalSection()都能够被执⾏到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执⾏。虽然临界区同步速度很快,但却只能⽤来同步本进程内的线程,⽽不可⽤来同步多个进程中的线程。CRITICAL_SECTION 所使⽤的头⽂件
发布者:admin,转转请注明出处:http://www.yc00.com/web/1687425250a9117.html
评论列表(0条)