免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 10345 | 回复: 10
打印 上一主题 下一主题

利用interlockedexchangeadd实现用户态的互斥 [复制链接]

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-07-31 13:20 |只看该作者 |倒序浏览
(相当于用户态的Spinlock),大家有什么想法,如何实现?
好像很难实现的样子啊,很早以前的问题了。题外话:不清楚为什么Linuz不使用Exchange而一定要使用Lock Inc这种方式,不知他怎么想的。

难不成使用:
long var=0;
interlockedexchangeadd(&var,1);

interlockedexchangeadd(&var,-1);

多使用了一次Interlocked,不但没有效率,不幸在激烈竞争时,效率更要指数下降,如果成千上万的线程(据说Linux支持线程了),可能成为“假死锁”(var很长很长时间无法回到0);

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
2 [报告]
发表于 2010-07-31 17:50 |只看该作者
interlockedexchangeadd?
不是InterlockedCompareExchange?

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
3 [报告]
发表于 2010-08-01 01:58 |只看该作者
谢谢回复

InterlockedCompareExchange
Linux下也没有这个函数,Windows有的,不过我不用~~

有的只是InterlockedExchageAddd,这个函数基本没有什么用.
PS我希望不要通过OS的API实现Mutex,这样有两个好处:1它更快(不管Windows或是Linux都一样,Call API要RUN成千上万个CPU Clock,只要看看上下文Switch的代价就无法接受了,更何况它还会建立内核对象)

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
4 [报告]
发表于 2010-08-01 04:05 |只看该作者
本帖最后由 OwnWaterloo 于 2010-08-01 04:08 编辑

终于看明白了……
算法大致是这样:

  1. #include <windows.h>

  2. typedef struct {
  3.       long enter;
  4.       long leave;
  5. } spinlock_t;

  6. void spinlock_acquire(spinlock_t* self)
  7. {
  8.       long enter = _InterlockedExchangeAdd(&self->enter, 1);
  9.       while (enter!=self->leave);
  10. }

  11. void spinlock_release(spinlock_t* self)
  12. {
  13.       ++self->leave;
  14. }
复制代码
有2个计数器, enter和leave, 记录流量: 进入计数和离开计数。
显然, 进入条件就是进入计数与离开计数相同。

进入时, 原子地增加enter并获得原值。
如果发生竞争, 肯定有一个最幸运的家伙拿到和leave相同的原值, 并进入。

其他的按登记顺序依次等待
最终离开时增加leave, 第2幸运的家伙就可以进入。


i486上long会回绕, 可以将计数器的取值看作一个"环":


  1.     0
  2.   7    1
  3. 6        2
  4.   5    3
  5.     4
复制代码
只要等待数不超过LONG_MAX即可。


linux(i486)中的实现没有使用两个long, 只用了32bit。
而且32bit都没有用完, 只用了ax。
将al当作leave, ah当作enter。

所以, 保险起见, 竞争者不能超过256。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
5 [报告]
发表于 2010-08-01 04:14 |只看该作者
本帖最后由 OwnWaterloo 于 2010-08-01 04:16 编辑

列出一些ref供参考……

首先是趴出的linux的源代码:

  1. /* arch/x86/include/asm/alternative.h */

  2. #ifdef CONFIG_SMP
  3. #define LOCK_PREFIX_HERE                  \
  4.       ".section .smp_locks,\"a\"\n"              \
  5.       ".balign 4\n"                              \
  6.       ".long 671f - .\n" /* offset */        \
  7.       ".previous\n"                              \
  8.       "671:"

  9. #define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "

  10. #else /* ! CONFIG_SMP */
  11. #define LOCK_PREFIX_HERE ""
  12. #define LOCK_PREFIX ""
  13. #endif

  14. /* arch/x86/include/asm/spinlock_types.h */
  15. typedef struct arch_spinlock {
  16.       unsigned int slock;
  17. } arch_spinlock_t;

  18. /* arch/x86/include/asm/spinlock.h */

  19. #ifdef CONFIG_X86_32
  20. # define LOCK_PTR_REG "a"
  21. # define REG_PTR_MODE "k"
  22. #else
  23. # define LOCK_PTR_REG "D"
  24. # define REG_PTR_MODE "q"
  25. #endif

  26. #if defined(CONFIG_X86_32) && \
  27.       (defined(CONFIG_X86_OOSTORE) || defined(CONFIG_X86_PPRO_FENCE))
  28. /*
  29. * On PPro SMP or if we are using OOSTORE, we use a locked operation to unlock
  30. * (PPro errata 66, 92)
  31. */
  32. # define UNLOCK_LOCK_PREFIX LOCK_PREFIX
  33. #else
  34. # define UNLOCK_LOCK_PREFIX
  35. #endif


  36.       #define  __always_inline
  37.       #define static

  38. static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
  39. {
  40.       short inc = 0x0100;

  41.       asm volatile (
  42.             LOCK_PREFIX "xaddw %w0, %1\n"
  43.             "1:\t"
  44.             "cmpb %h0, %b0\n\t"
  45.             "je 2f\n\t"
  46.             "rep ; nop\n\t"
  47.             "movb %1, %b0\n\t"
  48.             /* don't need lfence here, because loads are in-order */
  49.             "jmp 1b\n"
  50.             "2:"
  51.             : "+Q" (inc), "+m" (lock->slock)
  52.             :
  53.       : "memory", "cc");
  54. }

  55. static __always_inline int __ticket_spin_trylock(arch_spinlock_t *lock)
  56. {
  57.       int tmp, new;

  58.       asm volatile("movzwl %2, %0\n\t"
  59.             "cmpb %h0,%b0\n\t"
  60.             "leal 0x100(%" REG_PTR_MODE "0), %1\n\t"
  61.             "jne 1f\n\t"
  62.             LOCK_PREFIX "cmpxchgw %w1,%2\n\t"
  63.             "1:"
  64.             "sete %b1\n\t"
  65.             "movzbl %b1,%0\n\t"
  66.             : "=&a" (tmp), "=&q" (new), "+m" (lock->slock)
  67.             :
  68.       : "memory", "cc");

  69.       return tmp;
  70. }

  71. static __always_inline void __ticket_spin_unlock(arch_spinlock_t *lock)
  72. {
  73.       asm volatile(UNLOCK_LOCK_PREFIX "incb %0"
  74.             : "+m" (lock->slock)
  75.             :
  76.       : "memory", "cc");
  77. }
复制代码
编译后的汇编代码:

  1. 00000000 <___ticket_spin_lock>:
  2.    0:        8b 54 24 04                  mov    edx,DWORD PTR [esp+0x4]
  3.    4:        b8 00 01 00 00               mov    eax,0x100
  4.    9:        66 0f c1 02                  xadd   WORD PTR [edx],ax
  5.    d:        38 e0                        cmp    al,ah
  6.    f:        74 06                        je     17 <___ticket_spin_lock+0x17>
  7.   11:        f3 90                        pause  
  8.   13:        8a 02                        mov    al,BYTE PTR [edx]
  9.   15:        eb f6                        jmp    d <___ticket_spin_lock+0xd>
  10.   17:        c3                           ret   

  11. 00000018 <___ticket_spin_trylock>:
  12.   18:        8b 54 24 04                  mov    edx,DWORD PTR [esp+0x4]
  13.   1c:        0f b7 02                     movzx  eax,WORD PTR [edx]
  14.   1f:        38 e0                        cmp    al,ah
  15.   21:        8d 88 00 01 00 00            lea    ecx,[eax+0x100]
  16.   27:        75 04                        jne    2d <___ticket_spin_trylock+0x15>
  17.   29:        66 0f b1 0a                  cmpxchg WORD PTR [edx],cx
  18.   2d:        0f 94 c1                     sete   cl
  19.   30:        0f b6 c1                     movzx  eax,cl
  20.   33:        c3                           ret   

  21. 00000034 <___ticket_spin_unlock>:
  22.   34:        8b 44 24 04                  mov    eax,DWORD PTR [esp+0x4]
  23.   38:        fe 00                        inc    BYTE PTR [eax]
  24.   3a:        c3                           ret   
  25.   3b:        90                           nop   
复制代码
可以看出, trylock还是使用了cmpxchg , 也就是InterlockedCompareExchange。

最后是xaddcmpxchg

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
6 [报告]
发表于 2010-08-01 04:51 |只看该作者
再帖一个用InterlockedCompareExchange实现的:

  1. #include <windows.h>

  2. typedef struct {
  3.       long owner;
  4. } spinlock_t;
  5. enum { invalid_owner=0 };

  6. int spinlock_acquire_try_(spinlock_t* self, long id)
  7. {
  8.       return _InterlockedCompareExchange(&self->owner, id, invalid_owner)
  9.              == invalid_owner;
  10. }

  11. void spinlock_acquire(spinlock_t* self)
  12. {
  13.       long id = GetCurrentThreadId();
  14.       while (!spinlock_acquire_try_(self, id));
  15. }

  16. int spinlock_acquire_try(spinlock_t* self)
  17. {
  18.       long id = GetCurrentThreadId();
  19.       return spinlock_acquire_try_(self, id);
  20. }

  21. void spinlock_release(spinlock_t* self)
  22. {
  23.       self->owner = invalid_owner;
  24. }
复制代码
每个线程以id号作为标识。lock保存持有者的id。
Windows保证线程id不会为0, 所以用0作为特殊所持者, 表示锁可获取。

尝试进入时, 原子地比较id与owner。
若相等(可获取), 则将id写入owner, 以阻止其他线程获得锁。
若owner的原值为invalid_owner, 说明自己正好就是获得锁的线程。

进入时, 不断尝试直到成功。

离开时, 将锁标识为可获取。

这种算法依赖于:
1. 能获取线程id
2. id是不超过long的整数
3. 存在一个被保留的id

当时是一个同学申请场外援助时想出的, 题目给的就是compare_and_swap。
实现后觉得至少在windows上还行。
pthread_t好像不保证是整数, 也没说有保留id……
也没考虑过完全不依赖os api或者线程库……
然后也没继续深入了……

所以2楼会有那样的问题……

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
7 [报告]
发表于 2010-08-01 21:39 |只看该作者
回复 4# OwnWaterloo
    十分感谢!以前在哪个角落看过这个代码,那时感到实现得很XX,没在意,现在想想,的确有其巧妙处。
巧妙点在于,它按先后顺序给竞争者排队了。

下面的代码则无法做到这一点:

  1. volatile long spinLock=0;
  2. while(InterlockedExchage(&spinLock,1)){}
  3. Intermutex codes..
  4. spinLock=0;
复制代码

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
8 [报告]
发表于 2010-08-01 21:43 |只看该作者
再帖一个用InterlockedCompareExchange实现的:每个线程以id号作为标识。lock保存持有者的id。
Windows保证 ...
OwnWaterloo 发表于 2010-08-01 04:51



    谢谢,唯一的ID可以自已实现的~~,直接使用InerlockedAdd即可

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
9 [报告]
发表于 2010-08-02 14:13 |只看该作者
回复 8# folklore

这个怎么做???

论坛徽章:
59
2015年亚洲杯之约旦
日期:2015-01-27 21:27:392015年亚洲杯之日本
日期:2015-02-06 22:09:41拜羊年徽章
日期:2015-03-03 16:15:432015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015元宵节徽章
日期:2015-03-06 15:50:392015年亚洲杯之阿联酋
日期:2015-03-19 17:39:302015年亚洲杯之中国
日期:2015-03-23 18:52:23巳蛇
日期:2014-12-14 22:44:03双子座
日期:2014-12-10 21:39:16处女座
日期:2014-12-02 08:03:17天蝎座
日期:2014-07-21 19:08:47
10 [报告]
发表于 2010-08-02 16:36 |只看该作者
struct {
  long enter;
  long leave;
} uspin{enter:0;leave:0};

long thid=InterlockedExchageAdd(uspin.enter);  //此外本身产生了一个唯一的ID
while(thid==thid.leave){}
!@#$%^&*
uspin.leave++;

另以上代码因为是自已在用户态实现的,故没有AX的16Bit问题,可以允许2^32=4G个线程同时竞争(这是不可能的,因为uspin是本身应用于快速Acquire/Release的Case)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP