不使用系统API来实现互斥保护功能

yuxing_hui 2009-06-26 02:50:18

一般临界资源的互斥保护,需要使用类似take_mutex / give_mutex 类似的系统API来实现,

一般需要从用户空间切换到内核空间,有时候可能要关中断等,为了实现一个开销小的,

实现简单的临界资源的互斥保护机制,我设计了一个方法,希望大家能参考一下,给点建议。

volatile int a = 0;

volatile int b = 0;

/* 线程 A */

void thread_A()
{
while ( 1 ) {
a++;
if ( b == 0 ) {
access_the_critical_resource;
a = 0;
break;
} else {
a = 0;
}
}
}

/* 线程 B */


void thread_B()
{
while ( 1 ) {
b++;
if ( a == 0 ) {
access_the_critical_resource;
b = 0;
break;
} else {
b = 0;
}
}
}

现在来分析一下为什么这两个线程能实现互斥,

A线程首先将a加1,然后去加查b变量,如果b为0,说明B线程还没有将b加1,或者是B线程已经将b加1,但还没有写回到内存。总之B线程还运行在b++语句之前的某个位置,或者是刚执行完b=0的操作(即运行在b=0与b++之间的某个位置),所以我们断定B此时没有进入临界区,故A线程可以执行后面的临界区访问操作。

那么如果但A线程检查b变量时,如果b变量不等于0,那么此时B线程有可能在访问临界区,也有可能只是执行完了b++语句,但还没有进入临界区,此时A线程通过if ( b == 0 )的判断来避免了与B同时进入临界区的危险。

再分析,因为A线程在尝试进入临界区之前会将自己的开关变量a加1,所以一旦A互斥检查获得通过的话(if ( b == 0 )),就说明自己可以放心进入临界区了,因为此时可以肯定对方运行在b = 0;

与 b++;这两条语句之间,而不是b++与b=0之间(请注意这两条语句的先后顺序),所以当A进入了临界区,并处在临界区的这段时间内,B会被 if ( a == 0 )这个条件阻挡在临界区外,直到A出了临界区,执行a=0;break;,

此时B才有可能进入到临界区。

根据对称性,把A和 B反过来分析也一样,当然这种方法有很多缺点,比如极端情况下两个线程有可能都进不了

临界区等,我这里就不一一列举了,仅仅做个试验尝试而已。

在SMP的环境中,这种方法应该也可以,不会有逻辑错误。
...全文
97 25 打赏 收藏 转发到动态 举报
写回复
用AI写文章
25 条回复
切换为时间正序
请发表友善的回复…
发表回复
gemini_star 2009-07-16
  • 打赏
  • 举报
回复
应该不是cache一致性的问题。Intel的CPU通过Snoop协议已经在硬件上保证了cache的一致性。
yuxing_hui 2009-07-14
  • 打赏
  • 举报
回复
是的,多核下,线程可能在不同的CPU核上同时执行,因为每个CPU核都有自己独立的cache,所以有可能出现不一致的问题。
showjim 2009-07-12
  • 打赏
  • 举报
回复
[Quote=引用 22 楼 yuxing_hui 的回复:]
今天作了一个测试程序测试了一下,SMP环境下会出错,原因是多核CPU的cache一致性问题,
所以这个东东只能在单核CPU下用.
[/Quote]
多核下还有这种事真是出乎意料。谢谢楼主提醒,以后碰到多核的时候会注意这个问题的,不过我也会先测试一番。
yuxing_hui 2009-07-11
  • 打赏
  • 举报
回复
今天作了一个测试程序测试了一下,SMP环境下会出错,原因是多核CPU的cache一致性问题,
所以这个东东只能在单核CPU下用.
yuxing_hui 2009-07-02
  • 打赏
  • 举报
回复
楼上说的很有道理
arong1234 2009-07-01
  • 打赏
  • 举报
回复
一个访问共享资源的时间都是远远大于锁的开关时间的,所以开锁关锁这种效率损失几乎是可以忽略的

就如同一个马拉松选手,在起跑时弄个起跑器是不是能更快?
arong1234 2009-07-01
  • 打赏
  • 举报
回复
无论如何,你的实现方法代码复杂度就难以控制,而带来的好处几乎时可以忽略的
[Quote=引用 18 楼 yuxing_hui 的回复:]
to arong1234,
不好意思,我看错了,我以为你对我的代码有修改,其实没有改:),
上面你的描述有点问题,我指出来了, 你看我1楼的描述里有"
根据对称性,把A和 B反过来分析也一样,当然这种方法有很多缺点,比如极端情况下两个线程有可能都进不了"
所以说却是有可能两个线程都进不了互斥区, 但一定不会出现同时进入互斥区的情况.
[/Quote]
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
to arong1234,
不好意思,我看错了,我以为你对我的代码有修改,其实没有改:),
上面你的描述有点问题,我指出来了, 你看我1楼的描述里有"
根据对称性,把A和 B反过来分析也一样,当然这种方法有很多缺点,比如极端情况下两个线程有可能都进不了"
所以说却是有可能两个线程都进不了互斥区, 但一定不会出现同时进入互斥区的情况.
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
to arong1234,
你对代码的修改是正确的,但好像你的描述有点问题?
c) 线程A判断if(a==0) 失败,然后设置a=0
d) 线程B判断if(b==0) 失败,然后设置b=0

正确的描述应该是:
线程A 会去判断if(b==0),然后设置a=0;
线程b 会去判断if(a==0),然后设置b=0;
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
楼上的代码修改是正确的, 有两处增加应该是我遗漏的,很好,
mLee79 2009-07-01
  • 打赏
  • 举报
回复
当然要用 interlocked 函数, a++ 啥的是个典型的 rmw 的过程, volatile 不保证这个过程是原子的,如果 r 发生,w未完成时发生线程切换,特别是在多核的机器上,你这东东出问题几乎是必然的 。。。
你这个就是自旋锁,内核里广泛的被使用,没啥特别的。。。
arong1234 2009-07-01
  • 打赏
  • 举报
回复
这种方法代价高且不确保正确。
首先说正确性,正确的锁能保证资源总能按照机会均等原则被访问,我们假定由于操作系统调度算法的原因,下列序列被循环执行
a) 线程A调用a++
b) 线程B调用b++
c) 线程A判断if(a==0) 失败,然后设置a=0
d) 线程B判断if(b==0) 失败,然后设置b=0
你怎么确保这个序列不出现从而导致资源永远无法访问到

其次说效率:critical section会在线程尝试被调度时就阻止线程,因此在这个过程中线程是几乎完全不占用CPU的,如果说它上锁需要额外的资源,那么你循环使用CPU不也是浪费资源么?哪种浪费更多,恐怕你的不一定占优


void thread_A()
{
while ( 1 ) {
a++;
if ( b == 0 ) {
access_the_critical_resource;
a = 0;
break;
} else {
a = 0;
}
}
}

/* 线程 B */


void thread_B()
{
while ( 1 ) {
b++;
if ( a == 0 ) {
access_the_critical_resource;
b = 0;
break;
} else {
b = 0;
}
}
}

yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
to mLee79,
只是做一个设计而已,并没有考虑到性能,在某些特定的情况下是有用的,
比如有些情况下只需要测试一下共享资源是否可用,如果不可用就去做别的事,这种情况下效率会很高。
我给出的例子代码是不停的去测试共享资源,可以很容易的改造一下。
至于你说的“volatile 不能保证加减操作的原子性,你要用 interlocked 系列函数,incl ,decl , exchg , cas , cas 这些。。。”,
这些问题不存在,没有必要用到interlocked这些函数,也没有必要使用特殊的汇编指令,请再仔细分析。
mLee79 2009-07-01
  • 打赏
  • 举报
回复
想了下,你的确实在两个线程的时候没啥问题, 不过你考虑过5个线程的时候会出什么问题不。。。
条件写成 if( !b && !c && !d && !e ) ....
更多线程的时候会出什么问题, 对临界区的操作还有可能执行到不。。。

要知道写一个如此粗糙的自旋锁,10行代码完全足够了,而且每个线程都简单的写成 if( !spinlock_try_lock( a ) ) {} else {} ...
mLee79 2009-07-01
  • 打赏
  • 举报
回复
你还是自己仔细想想先,在 a++ 时,读了但还未回写结果的时候, 切到线程B 这时候会发生什么。。。
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
此处的volatile 并不是要保证读写原子性,只是为了保证每次都从内存中进行读写,而不会被编译器优化成寄存器变量。
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
自旋锁是用在SMP环境下的,需要锁住内存总线进行读写,我这个不需要锁,对非SMP也适用,
yuxing_hui 2009-07-01
  • 打赏
  • 举报
回复
to mLee79,
只能说明你没有仔细分析,不管a++是个什么过程,原不原子都不会出错,请再仔细分析。
mLee79 2009-06-30
  • 打赏
  • 举报
回复
你这个就个自旋锁,如果在用户态执行一个比较耗时的临界操作,性能将极端低下。。。
volatile 不能保证加减操作的原子性,你要用 interlocked 系列函数,incl ,decl , exchg , cas , cas 这些。。。
WizardOz 2009-06-30
  • 打赏
  • 举报
回复
效率是不是太低了?等于一直只有一个线程没有在做死循环。
加载更多回复(5)

33,028

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧