我是靠谱客的博主 花痴香菇,最近开发中收集的这篇文章主要介绍C++:线程操作之CRITICAL_SECTION用法的介绍和例子理解介绍实例编辑1实例编辑2,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

CRITICAL_SECTION

  • 介绍
  • 实例编辑1
    • 示例
  • 实例编辑2

介绍

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->str.Format(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->str.Format(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: 1
threadOne: 2
threadOne: 3
threadOne: 4
threadOne: 5
threadOne: 6
threadOne: 7
threadOne: 8
threadOne: 9
threadOne: 10
threadSecond: 11
threadSecond: 12
threadSecond: 13
threadSecond: 14
threadSecond: 15
threadSecond: 16
threadSecond: 17
threadSecond: 18
threadSecond: 19
threadSecond: 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->str.Format(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->str.Format(pthis->str + “nthreadSecond: %drn”, pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
return 0;
}
执行结果为(都可以访问资源,同步执行):
threadOne: 1
threadSecond: 2
threadOne: 3
threadSecond: 4
threadOne: 5
threadSecond: 6
threadOne: 7
threadSecond: 8
threadOne: 9
threadSecond: 10
threadOne: 11
threadSecond: 12
threadOne: 13
threadSecond: 14
threadOne: 15
threadSecond: 16
threadOne: 17
threadSecond: 18
threadOne: 19
threadSecond: 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->str.Format(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->str.Format(pthis->str + “nthreadSecond: %drn”, pthis->value);
pthis->SetDlgItemTextA(IDC_EDIT1, pthis->str);
}
LeaveCriticalSection(&pthis->g_cs2);
return 0;
}
执行结果(同步执行):
threadOne: 1
threadSecond: 2
threadOne: 3
threadSecond: 4
threadOne: 5
threadSecond: 6
threadOne: 7
threadSecond: 8
threadOne: 9
threadSecond: 10
threadOne: 11
threadSecond: 12
threadOne: 13
threadSecond: 14
threadOne: 15
threadSecond: 16
threadOne: 17
threadSecond: 18
threadOne: 19
threadSecond: 20

在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
CRITICAL_SECTION 所使用的头文件<windows.h>

最后

以上就是花痴香菇为你收集整理的C++:线程操作之CRITICAL_SECTION用法的介绍和例子理解介绍实例编辑1实例编辑2的全部内容,希望文章能够帮你解决C++:线程操作之CRITICAL_SECTION用法的介绍和例子理解介绍实例编辑1实例编辑2所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(73)

评论列表共有 0 条评论

立即
投稿
返回
顶部