临界区(关键段):线程同步方式,用于限制“公用代码”一次只能被一个线程使用
关键段定义
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;//调试用
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount; //初始化为-1,n表示有n个线程在等待
LONG RecursionCount; //表示该关键段的拥有线程对此资源获得关键段次数,初初始化为0
HANDLE OwningThread; //即拥有该关键段的线程句柄
HANDLE LockSemaphore; //实际上是一个自复位事件
ULONG_PTR SpinCount; //旋转锁的设置,单CPU下忽略
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
InitializeCriticalSection初始化
定义关键段变量后必须先初始化
/** \brief 初始化一个临界资源对象
*
* \param lpCriticalSection 指向临界区对象的指针
* \return 无
*
*/
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
DeleteCriticalSection销毁
用完之后记得销毁临界资源对象
/** \brief 销毁一个临界资源对象
*
* \param lpCriticalSection 指向临界区对象的指针
* 先前必须已将该对象初始化 InitializeCriticalSection初始化函数
* \return 无
*
*/
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
EnterCriticalSection进入关键区域
/** \brief 进入关键区域
*
* \param lpCriticalSection 指向临界区对象的指针
* \return 无
*
*/
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
LeaveCriticalSection离开关键区域
/** \brief 离开关键区域
*
* \param lpCriticalSection 指向临界区对象的指针
* \return 无
*
*/
void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
示例1
所有子线程都访问共享资源,为了避免冲突,需要加上关键段,保证每次共享资源只能被一个线程访问。在子线程中每访问共享资源则记录一次。
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#define THREAD_COUNT 30U //子线程数目
void Thread(void*);
volatile int count = 0;//共享资源
CRITICAL_SECTION cs1;//关键段变量
int main()
{
HANDLE th[THREAD_COUNT] = { 0 };
int num = 30;
/* 初始化关键段 */
InitializeCriticalSection(&cs1);
//模拟次数
while (num--)
{
count = 0;//重置
for (size_t i = 0; i < THREAD_COUNT; i++)
{
/* 创建线程 */
th[i] = _beginthread(Thread, 0, NULL);
}
//等待所有子线程结束
WaitForMultipleObjects(THREAD_COUNT, th, TRUE, INFINITE);
printf("共 %d 个子线程 共享资源次数为%d\n", THREAD_COUNT, count);
}
/* 销毁关键段 */
DeleteCriticalSection(&cs1);
return 0;
}
void Thread(void* arg)
{
Sleep(100);
/* 进入关键段 */
EnterCriticalSection(&cs1);
count++;//子线程中 每访问一次共享资源则记录一次
/* 离开关键段 */
LeaveCriticalSection(&cs1);
}
输出结果
示例2
与示例1要求一样,只不过只有三个子线程。线程A将访问次数加1,线程B将访问次数加50,线程C将访问次数加100。
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#define THREAD_COUNT 3U //子线程数目
void Thread1(void*);
void Thread2(void*);
void Thread3(void*);
volatile int count = 0;//共享资源
CRITICAL_SECTION cs1;//关键段变量
int main()
{
HANDLE th[THREAD_COUNT] = { 0 };
int num = 30;
/* 初始化关键段 */
InitializeCriticalSection(&cs1);
//模拟次数
while (num--)
{
count = 0;//重置
/* 创建子线程 */
th[0] = _beginthread(Thread1, 0, NULL);
th[1] = _beginthread(Thread2, 0, NULL);
th[2] = _beginthread(Thread3, 0, NULL);
//等待所有子线程结束
WaitForMultipleObjects(THREAD_COUNT, th, TRUE, INFINITE);
printf("共 %d 个子线程 共享资源次数为%d\n", THREAD_COUNT, count);
}
/* 销毁关键段 */
DeleteCriticalSection(&cs1);
return 0;
}
void Thread1(void* arg)
{
Sleep(100);
/* 进入关键段 */
EnterCriticalSection(&cs1);
count++;//子线程中 每访问一次共享资源则记录一次
/* 离开关键段 */
LeaveCriticalSection(&cs1);
}
void Thread2(void* arg)
{
Sleep(100);
/* 进入关键段 */
EnterCriticalSection(&cs1);
count += 50;//访问次数翻倍
/* 离开关键段 */
LeaveCriticalSection(&cs1);
}
void Thread3(void* arg)
{
Sleep(100);
/* 进入关键段 */
EnterCriticalSection(&cs1);
count += 100;//访问次数加100
/* 离开关键段 */
LeaveCriticalSection(&cs1);
}
输出结果
说明,每个需要访问共享资源的线程,都需要加上关键段来限制共享资源每次只被一个线程访问,如果有一个线程没有加上关键段的限制,那么关键段将没有意义。
如
void Thread3(void* arg)
{
Sleep(100);
count += 100;//访问次数加100
}
那么可能会出现
旋转锁
当线程试图进入另一个线程拥有的关键代码段时,调用线程就立即被置于等待状态。这意
味着该线程必须从用户方式转入内核方式(大约1 0 0 0个C P U周期)。这种转换是要付出很大代价的。
因此, InitializeCriticalSectionAndSpinCount 的作用不同于InitializeCriticalSection 之处就在于设置了一个循环锁,不至于使线程立刻被置于等待状态而耗费大量的CPU周期,而在dwSpinCount后才转为内核方式进入等待状态。通常dwSpinCount设为4000较为合适。
下面是配合了旋转锁的关键段初始化函数
初始化关键段并设置旋转次数
/** \brief 初始化关键段并设置旋转次数
*
* \param lpCriticalSection 临界资源对象指针
* \param dwSpinCount 临界区对象的旋转计数。在单处理器系统上,自旋计数被忽略,临界区自旋计数被设置为0
* \return 成功并返回一个非零值
*
*/
BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);
修改关键段的旋转次数
/** \brief 修改关键段的旋转次数
*
* \param lpCriticalSection 临界资源对象指针
* \param dwSpinCount 临界区对象的旋转计数。在单处理器系统上,自旋计数被忽略,临界区自旋计数被设置为0
* \return 返回临界区的前一次旋转计数
*
*/
DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);