首先前面提到了一个思路:给队列模型添加初步的线程保护,在使用它的时候,可以不考虑会保护其免受资源争夺的问题
CRITICAL_SECTION
放在 Queue.c
的实现当中。PushBack
PopFront
能够自己实现保护自己empty
实现了单独保护,现在反过来,将其保护范围扩大一些就行了。具体方法 Queue.c
首先是取消使用 empty_sec
这个关键段/临界区
使用新的 static CRITICAL_SECTION io_section;
修改 newQueue
和 del_queue
里的初始化和销毁关键段代码。
以及重点的 push_back
和 pop_front
的代码修改,前者变化多一些
static int push_back(queue * __restrict object, const char * __restrict src, const char * __restrict dst)
{
char* loc_src = NULL; /* 本地变量,尽量利用寄存器以及缓存 */
char* loc_dst = NULL;
combine* loc_com = NULL;
queue* loc_que = object;
size_t len_src = strlen(src); /* 获取路径长度 */
size_t len_dst = strlen(dst);
size_t rear = 0; /* 队列的队尾 */
size_t front = 0; /* 队列的队首 */
loc_src = Malloc_s(len_src + 1); /* 分配空间 */
loc_dst = Malloc_s(len_dst + 1);
loc_com = Malloc_s(sizeof(combine));
if (loc_src == NULL || loc_dst == NULL || loc_com == NULL)
{
Free_s(loc_src); /* 特殊处理过的释放函数 */
Free_s(loc_dst);
Free_s(loc_src);
return 1;
}
strcpy(loc_src, src); /* 构造路径模型 */
strcpy(loc_dst, dst);
loc_com->dst_to_path = loc_dst;
loc_com->src_from_path = loc_src;
/* 进入保护 */
EnterCriticalSection(&io_section);
rear = loc_que->rear; /*获取队尾*/
front = loc_que->front; /*获取队首*/
loc_que->path_contain[rear++] = loc_com; /* 将本地路径加入实体 */
loc_que->rear = (rear % CAPCITY); /* 用数组实现循环队列的步骤 */
/* 取消原先的保护 */
if (loc_que->rear == loc_que->front)
{
loc_que->empty = 0;
}
LeaveCriticalSection(&io_section);
return 0;
}
注释里写了很多信息,主要教之前的版本改变了一下串行代码的顺序,功能并没有太大变化,变化的两处地方,一个是内存分配错误判断由三个if
变成一个if
,另一个是为了使临界区内的代码尽可能少,所以将一些操作移动了。
`pop_front`代码基本没改变只是将临界区扩大了保护范围。
static combine * pop_front(queue* object)
{
EnterCriticalSection(&io_section);
size_t loc_front = object->front; /*获取当前队首*/
...
// EnterCriticalSection(&empty_sec); /* 原先的临界区起始 */
if (object->front == object->rear)
object->empty = 1;
else
object->empty = 0;
// LeaveCriticalSection(&empty_sec);
LeaveCriticalSection(&io_section);
return loc_com;
}
如此修改以后,该队列模型就具备了初步的线程安全功能。
在主代码中,可以删除 PopFront
和 PushBack
附近的保护操作。
前方提到了,CRITICAL_SECTION
相对于 Mutex
不太安全,这里简单说一下,具体请查询相关资料
CRITICAL_SECTION
只保证在同一个**"时间"内,只有一个线程能够运行这段代码**,假设我在其他代码还有对这段代码中的资源进行访问,那关键段就不能保证什么了。发现问题了吗?
Mutex
的保护,而不是使用CRITICAL_SECTION
Mutex
吗?其实有一个更好的选择,那就是在 Windows Vista之后引入的一个读写锁SRWLOCK
,允许多个线程读取数据或者单个线程写入数据
empty
的关键段保护修改或添加上SRWLOCK
读写锁的保护AcquireSRWLock(Exclusive/Shared)
),离开保护区释放(ReleaseSRWLock(Exclusive/Shared)
)。TryAcquire...
操作,但是确在Windows 7以后才引入,故不在此实现。写锁饿死,是个挺广泛的概念,只要有读写锁的身影就必定有它,可以查阅相关资料
这一切都是需要测试,测试,再测试,之所以选择在 Windows 平台上开发,原因之一就是Visual Studio有一套强大到爆表的性能分析工具,你可以轻易找出代码性能问题。善用它来研究不同锁之间的差别,性能。
对于多线程程序而言,同步原语实际上还是要善用 条件变量/互斥锁(临界区,关键段)这两个概念,能满足90% 的需求,最多再使用一些平台关键字就行了,不要参杂着各种各样的同步魔法,要不就换一种思维,除了 多线程,并发世界还有许多**“很美好”**的东西
很美好。。。是我自己说的