热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

futex函数_UNIX/LINUX

futex函数_UNIXLINUX引子在编译2.6内核的时候,你会在编译选项中看到[*]Enablefutexsupport这一项,上网查,有的资料会告诉你不选这个内核不一定能正


Linux中的线程同步机制(二)–In Glibc

在linux中进行多线程开发,同步是不可回避的一个问题。
在POSIX标准中定义了三种线程同步机制: Mutexes(互斥量), Condition Variables(条件变量)和POSIX Semaphores(信号量)
。NPTL基本上实现了POSIX,而glibc又使用NPTL作为自己的线程库。因此glibc中包含了这三种同步机制 的实现(当然还包括其他的同步机制,如APUE里提到的读写锁)。




Glibc中常用的线程同步方式举例:




Semaphore


变量定义:    sem_t sem;


初始化:      sem_init(&sem,0,1);


进入加锁:     sem_wait(&sem);


退出解锁:     sem_post(&sem);




Mutex


变量定义:    pthread_mutex_t mut;


初始化:      pthread_mutex_init(&mut,NULL);


进入加锁:     pthread_mutex_lock(&mut);


退出解锁:     pthread_mutex_unlock(&mut);






这些用于同步的函数和futex有什么关系?下面让我们来看一看:


以Semaphores为例,


进入互斥区的时候,会执行sem_wait(sem_t *sem),sem_wait的实现如下:


int sem_wait (sem_t *sem)


{



int *futex = (int *) sem;


if (atomic_decrement_if_positive (futex) > 0)


    return 0;


int   err = lll_futex_wait (futex, 0);


    return -1;


)


atomic_decrement_if_positive()的语义就是如果传入参数是正数就将其原子性的减一并立即返回。如果信号量为正,在Semaphores的语义中意味着没有竞争发生,如果没有竞争,就给信号量减一后直接返回了。




如果传入参数不是正数,即意味着有竞争,调用lll_futex_wait(futex,0),lll_futex_wait是个宏,展开后为:


#define lll_futex_wait(futex, val) \


({                                          \


    …


    __asm __volatile (LLL_EBX_LOAD                          \


              LLL_ENTER_KERNEL                          \


              LLL_EBX_LOAD                          \


              : “=a” (__status)                          \


              : “0” (SYS_futex), LLL_EBX_REG (futex), “S” (0),          \


            “c” (FUTEX_WAIT), “d” (_val),                  \


            “i” (offsetof (tcbhead_t, sysinfo))              \


              : “memory”);                          \


    …                                      \


})


可以看到当发生竞争的时候,sem_wait会调用SYS_futex系统调用,并在val=0的时候执行FUTEX_WAIT,让当前线程休眠。




从 这个例子我们可以看出,在Semaphores的实现过程中使用了futex,不仅仅是说其使用了futex系统调用(再重申一遍只使用futex系统调 用是不够的),而是整个建立在futex机制上,包括用户态下的操作和核心态下的操作。其实对于其他glibc的同步机制来说也是一样,都采纳了 futex作为其基础。所以才会在futex的manual中说:对于大多数程序员不需要直接使用futexes,取而代之的是依靠建立在futex之上 的系统库,如NPTL线程库(most programmers will in fact not be using futexes directly but instead rely on system libraries built on them, such as the NPTL pthreads implementation)。所以才会有如果在编译内核的时候不 Enable futex support,就”不一定能正确的运行使用Glibc的程序”。




小结:


1. Glibc中的所提供的线程同步方式,如大家所熟知的Mutex,Semaphore等,大多都构造于futex之上了,除了特殊情况,大家没必要再去实现自己的futex同步原语。
2. 大家要做的事情,似乎就是按futex的manual中所说得那样: 正确的使用Glibc所提供的同步方式,并在使用它们的过程中,意识到它们是利用futex机制和linux配合完成同步操作就可以了。




Linux中的线程同步机制(三)–Practice



上回说到Glibc中(NPTL)的线程同步方式如Mutex,Semaphore等都使用了futex作为其基础。那么实际使用是什么样子,又会碰到什么问题呢?


先来看一个使用semaphore同步的例子。




sem_t sem_a;


void *task1();




int main(void){



int ret=0;


pthread_t thrd1;


sem_init(&sem_a,0,1);


ret=pthread_create(&thrd1,NULL,task1,NULL); //创建子线程


pthread_join(thrd1,NULL); //等待子线程结束


}




void *task1()


{



int sval = 0;


sem_wait(&sem_a); //持有信号量


sleep(5); //do_nothing


sem_getvalue(&sem_a,&sval);


printf(“sem value = %d\n”,sval);


sem_post(&sem_a); //释放信号量


}




程序很简单,我们在主线程(执行main的线程)中创建了一个线程,并用join等待其结束。在子线程中,先持有信号量,然后休息一会儿,再释放信号量,结束。


因为这段代码中只有一个线程使用信号量,也就是没有线程间竞争发生,按照futex的理论,因为没有竞争,所以所有的锁操作都将在用户态中完成,而不会执行系统调用而陷入内核。我们用
strace
来跟踪一下这段程序的执行过程中所发生的系统调用:


…


20533 futex(0xb7db1be8, FUTEX_WAIT, 20534, NULL


20534 futex(0x8049870, FUTEX_WAKE, 1)   = 0


20533 <&#8230; futex resumed> )             = 0


&#8230; 


20533是main线程的id,20534是其子线程的id。出乎我们意料之外的是这段程序还是发生了两次futex系统调用,我们来分析一下这分别是什么原因造成的。




1. 
出人意料的&#8221;sem_post()&#8221;


20534 futex(0x8049870, FUTEX_WAKE, 1)   = 0


子 线程还是执行了FUTEX_WAKE的系统调用,就是在sem_post(&sem_a);的时候,请求内核唤醒一个等待在sem_a上的线程, 其返回值是0,表示现在并没有线程等待在sem_a(这是当然的,因为就这么一个线程在使用sem_a),这次futex系统调用白做了。这似乎和 futex的理论有些出入,我们再来看一下sem_post的实现。


int sem_post (sem_t *sem)


{



int *futex = (int *) sem;


int nr = atomic_increment_val (futex);


int err = lll_futex_wake (futex, nr);


return 0;


}


我们看到,Glibc在实现sem_post的时候给futex原子性的加上1后,不管futex的值是什么,都执行了lll_futex_wake(),即futex(FUTEX_WAKE)系统调用。


在 第二部分中(见前文),我们分析了sem_wait的实现,当没有竞争的时候是不会有futex调用的,现在看来真的是这样,但是在sem_post的时 候,无论有无竞争,都会调用sys_futex(),为什么会这样呢?我觉得应该结合semaphore的语义来理解。
在semaphore的语义 中,sem_wait()的意思是:&#8221;挂起当前进程,直到semaphore的值为非0,它会原子性的减少semaphore计数值。&#8221; 
我们可以看到,semaphore中是通过0或者非0来判断阻塞或者非阻塞线程。即无论有多少线程在竞争这把锁,只要使用了 semaphore,semaphore的值都会是0。这样,
当线程推出互斥区,执行sem_post(),释放semaphore的时候,将其值由0改 1,并不知道是否有线程阻塞在这个semaphore上,所以只好不管怎么样都执行futex(uaddr, FUTEX_WAKE, 1)尝试着唤醒一个进程。
而相反的,当sem_wait(),如果semaphore由1变0,则意味着没有竞争发生,所以不必去执行futex系统调 用。我们假设一下,如果抛开这个语义,如果允许semaphore值为负,则也可以在sem_post()的时候,实现futex机制。




2. 半路杀出的&#8221;pthread_join()&#8221;


那另一个futex系统调用是怎么造成的呢? 是因为pthread_join();


在Glibc中,pthread_join也是用futex系统调用实现的。程序中的pthread_join(thrd1,NULL); 就对应着 


20533 futex(0xb7db1be8, FUTEX_WAIT, 20534, NULL


很 好解释,主线程要等待子线程(id号20534上)结束的时候,调用futex(FUTEX_WAIT),并把var参数设置为要等待的子线程号 (20534),然后等待在一个地址为0xb7db1be8的futex变量上。当子线程结束后,系统会负责把主线程唤醒。于是主线程就


20533 <&#8230; futex resumed> )             = 0


恢复运行了。


要注意的是,如果在执行pthread_join()的时候,要join的线程已经结束了,就不会再调用futex()阻塞当前进程了。




3. 更多的竞争。


我们把上面的程序稍微改改: 


在main函数中:


int main(void){



&#8230;


sem_init(&sem_a,0,1);


ret=pthread_create(&thrd1,NULL,task1,NULL);


ret=pthread_create(&thrd2,NULL,task1,NULL);


ret=pthread_create(&thrd3,NULL,task1,NULL);


ret=pthread_create(&thrd4,NULL,task1,NULL);


pthread_join(thrd1,NULL);


pthread_join(thrd2,NULL);


pthread_join(thrd3,NULL);


pthread_join(thrd4,NULL);


&#8230;


}




这样就有更的线程参与sem_a的争夺了。我们来分析一下,这样的程序会发生多少次futex系统调用。


1) sem_wait()


    第一个进入的线程不会调用futex,而其他的线程因为要阻塞而调用,因此sem_wait会造成3次futex(FUTEX_WAIT)调用。


2) sem_post()


    所有线程都会在sem_post的时候调用futex, 因此会造成4次futex(FUTEX_WAKE)调用。


3) pthread_join()


    别忘了还有pthread_join(),我们是按thread1, thread2, thread3, thread4这样来join的,但是线程的调度存在着随机性。如果thread1最后被调度,则只有thread1这一次futex调用,所以 pthread_join()造成的futex调用在1-4次之间。(虽然不是必然的,但是4次更常见一些)    


所以这段程序至多会造成3+4+4=11次futex系统调用,用strace跟踪,验证了我们的想法。


19710 futex(0xb7df1be8, FUTEX_WAIT, 19711, NULL


19712 futex(0x8049910, FUTEX_WAIT, 0, NULL


19713 futex(0x8049910, FUTEX_WAIT, 0, NULL


19714 futex(0x8049910, FUTEX_WAIT, 0, NULL


19711 futex(0x8049910, FUTEX_WAKE, 1


19710 futex(0xb75f0be8, FUTEX_WAIT, 19712, NULL


19712 futex(0x8049910, FUTEX_WAKE, 1


19710 futex(0xb6defbe8, FUTEX_WAIT, 19713, NULL


19713 futex(0x8049910, FUTEX_WAKE, 1


19710 futex(0xb65eebe8, FUTEX_WAIT, 19714, NULL


19714 futex(0x8049910, FUTEX_WAKE, 1)   = 0


(19710是主线程,19711,19712,19713,19714是4个子线程)




4. 更多的问题


事 情到这里就结束了吗? 
如果我们把semaphore换成Mutex试试。你会发现当自始自终没有竞争的时候,mutex会完全符合futex机制,不管是lock还是 unlock都不会调用futex系统调用。有竞争的时候,第一次pthread_mutex_lock的时候不会调用futex调用,看起来还正常。但 是最后一次pthread_mutex_unlock的时候,虽然已经没有线程在等待mutex了,可还是会调用futex(FUTEX_WAKE)。原因是什么?欢迎讨论!!!




小结:


1. 虽然semaphore,mutex等同步方式构建在futex同步机制之上。然而受其语义等的限制,并没有完全按futex最初的设计实现。
2. pthread_join()等函数也是调用futex来实现的。
3. 不同的同步方式都有其不同的语义,不同的性能特征,适合于不同的场景。我们在使用过程中要知道他们的共性,也得了解它们之间的差异。这样才能更好的理解多线程场景,写出更高质量的多线程程序。





转载地址:


http://blog.csdn.net/Javadino/archive/2008/09/06/2891385.aspx


http://blog.csdn.net/Javadino/archive/2008/09/06/2891388.aspx

http://blog.csdn.net/Javadino/archive/2008/09/06/2891399.aspx

Linux中的线程同步机制(四)&#8211;C语言实现


futex 的逻辑可以用如下C语言表示

int val = 0;
void lock()
{

    int c
    if ((c = cmpxchg(val, 0, 1)) != 0) {

        if (c != 2)
            c = xchg(val, 2);
        while (c != 0) {

            futex_wait((&val, 2);
            c = xchg(val, 2);
        }
    }
}   
    
void unlock()
{   
    if (atomic_dec(val) != 1)
        futex_wake(&val, 1);
}


val 0: unlock

val 1: lock, no waiters

val2 : lock , one or more waiters


推荐阅读
  • 在AngularJS中,有时需要在表单内包含某些控件,但又不希望这些控件导致表单变为脏状态。例如,当用户对表单进行修改后,表单的$dirty属性将变为true,触发保存对话框。然而,对于一些导航或辅助功能控件,我们可能并不希望它们触发这种行为。 ... [详细]
  • 数据输入验证与控件绑定方法
    本文提供了多种数据输入验证函数及控件绑定方法的实现代码,包括电话号码、数字、传真、邮政编码、电子邮件和网址的验证,以及报表绑定和自动编号等功能。 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文详细介绍了在PHP中如何获取和处理HTTP头部信息,包括通过cURL获取请求头信息、使用header函数发送响应头以及获取客户端HTTP头部的方法。同时,还探讨了PHP中$_SERVER变量的使用,以获取客户端和服务器的相关信息。 ... [详细]
  • 使用jQuery与百度地图API实现地址转经纬度功能
    本文详细介绍了如何利用jQuery和百度地图API将地址转换为经纬度,包括申请API密钥、页面构建及核心代码实现。 ... [详细]
  • 使用 ModelAttribute 实现页面数据自动填充
    本文介绍了如何利用 Spring MVC 中的 ModelAttribute 注解,在页面跳转后自动填充表单数据。主要探讨了两种实现方法及其背后的原理。 ... [详细]
  • HDU 2537 键盘输入处理
    题目描述了一个名叫Pirates的男孩想要开发一款键盘输入软件,遇到了大小写字母判断的问题。本文提供了该问题的解决方案及实现方法。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • 本文探讨了Linux环境下线程私有数据(Thread-Specific Data, TSD)的概念及其重要性,介绍了如何通过TSD技术避免多线程间全局变量冲突的问题,并提供了具体的实现方法和示例代码。 ... [详细]
  • C/C++ 应用程序的安装与卸载解决方案
    本文介绍了如何使用Inno Setup来创建C/C++应用程序的安装程序,包括自动检测并安装所需的运行库,确保应用能够顺利安装和卸载。 ... [详细]
  • 【MySQL】frm文件解析
    官网说明:http:dev.mysql.comdocinternalsenfrm-file-format.htmlfrm是MySQL表结构定义文件,通常frm文件是不会损坏的,但是如果 ... [详细]
  • WebBenchmark:强大的Web API性能测试工具
    本文介绍了一款名为WebBenchmark的Web API性能测试工具,该工具不仅支持HTTP和HTTPS服务的测试,还提供了丰富的功能来帮助开发者进行高效的性能评估。 ... [详细]
  • Kubernetes Services详解
    本文深入探讨了Kubernetes中的服务(Services)概念,解释了如何通过Services实现Pods之间的稳定通信,以及如何管理没有选择器的服务。 ... [详细]
  • 本文介绍如何通过Java代码调用阿里云短信服务API来实现短信验证码的发送功能,包括必要的依赖添加和关键代码示例。 ... [详细]
  • 本文分享了作者在使用LaTeX过程中的几点心得,涵盖了从文档编辑、代码高亮、图形绘制到3D模型展示等多个方面的内容。适合希望深入了解LaTeX高级功能的用户。 ... [详细]
author-avatar
开卷_汤娅造_673
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有