【C语言】Windows下的多线程编程-关键段(临界区)

临界区(关键段):线程同步方式,用于限制“公用代码”一次只能被一个线程使用

关键段定义

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);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
临界是指在多线编程中,多个线程共享的临界资源对应的代码片段。例如,在一个银行存取款的场景中,假设有多个客户同时进行存取款操作,每个客户对应一个线程。为了保证数据的一致性和正确性,需要通过临界来实现线程的互斥访问。 在C语言中,可以使用互斥锁(mutex)来实现临界的互斥访问。互斥锁是一种同步机制,用于保护共享资源,确保同时只有一个线程可以访问临界。下面是一个简单的银行存取款的示例: ``` #include <stdio.h> #include <pthread.h> int balance = 1000; // 银行账户余额 pthread_mutex_t mutex; // 互斥锁 void* deposit(void* arg) { pthread_mutex_lock(&mutex); // 上锁 int* amount = (int*)arg; balance += *amount; printf("存款 %d 元,余额为 %d 元\n", *amount, balance); pthread_mutex_unlock(&mutex); // 解锁 return NULL; } void* withdraw(void* arg) { pthread_mutex_lock(&mutex); // 上锁 int* amount = (int*)arg; if (balance >= *amount) { balance -= *amount; printf("取款 %d 元,余额为 %d 元\n", *amount, balance); } else { printf("余额不足\n"); } pthread_mutex_unlock(&mutex); // 解锁 return NULL; } int main() { pthread_t tid1, tid2; int amount1 = 500; int amount2 = 200; pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 pthread_create(&tid1, NULL, deposit, &amount1); // 创建存款线程 pthread_create(&tid2, NULL, withdraw, &amount2); // 创建取款线程 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&mutex); // 销毁互斥锁 return 0; } ``` 在这个示例中,通过互斥锁mutex来保护临界资源balance。存款和取款操作都加了互斥锁,确保每次只有一个线程访问临界。这样就避免了多个线程同时修改balance导致数据不一致的问题。最后,通过pthread_mutex_init()初始化互斥锁,pthread_mutex_lock()上锁,pthread_mutex_unlock()解锁,pthread_mutex_destroy()销毁互斥锁来完成对临界的互斥访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值