深化浅出函数调用的外部机制-之call-揭秘C (深入浅出数据)
引言
最远由于名目要求用c++,之前不时很厌恶c++,没方法只能短期间补偿c++的常识,名目中须要一个接口只调用一次性,须要经常使用到c++的call_once机制,于是写一个小demo来测试,就由于这个足够小发现了一个十分无心思的疑问。
call_once,基本原理
std::call_once的外部成功基于两个关键的组件:std::once_flag和std::invoke。std::once_flag是一个标志,用于示意某个函数能否曾经被调用过。而std::invoke则担任实践调用该函数。
call_once的基本上班原理是:经常使用std::once_flag来标志函数能否被调用过。当有多个线程试图调用std::call_once时,只要一个线程会口头函数,其余线程会被阻塞直至该函数口头终了。
std::call_once的经常使用步骤三步曲:
demo疑问引入
demo十分方便,成功一个Init函数启动call_once调用,只调用一次性的函数Initialize做一次性打印处置,mn中延续调用Init4次,通常过去说咱们口头结果只要一行打印,这也是咱们的目的。
#include<tream>#include<thread>#include<mutex>#include<atomic>std::once_flagflag;voidInitialize(){std::cout<<"RunintoInitialize.."<<std::endl;}voidInit(){std::call_once(flag,Initialize);}intmain(){Init();Init();Init();Init();return0;}
经常使用g++编译,口头结果发现出错了:
抛出了个意外,从call_once上的了解来说代码成功应该是没疑问的。于是经常使用调试大法gdb,编译+g后经常使用gdb调试发现了个无心思的:
经常使用gdb调试发现__gthread_active_ptr指针是0,而后继续口头发现___gthread_once前往的__e为0,于是继续口头if就抛了意外。__gthread_active_ptr这又是什么呢?
深化钻研钻研
怎样看呢?既然不知道是什么我普通的操作是先看预处置局部代码,经常使用gcc-E参数来编译出call_once.i文件。
g++-Ecall_once.cpp-ocall_once.i
关上call_once.i文件我发现main函数局部没有什么特意之处,咱们搜查call_once可以看到它的成功。
这段代码中的std::call_once函数首先创立了一个可调用对象__callable,这个对象会调用传入的函数__f,并传入__args参数。而后,它将__callable的地址存储到__once_callable变量中。
接上去,经过一个lambda表白式将__callable的调用封装在__once_call中,这个lambda表白式会口头__callable。
最后,经常使用底层线程库的__gthread_once函数来确保__once_call只会口头一次性,即保障传入的函数__f只会被调用一次性。
假设__gthread_once的前往值不为零,示意口头产生了失误,会经过__throw_system_error抛出系统失误。
既然是if(__e)后抛的意外,咱们继续看__gthread_once的成功,搜查__gthread_once关键字,找到其成功:
11452staticinlineint11453__gthread_once(__gthread_once_t*__once,void(*__func)(void))11454{11455if(__gthread_active_p())11456return__gthrw_pthread_once(__once,__func);11457else11458return-1;11459}
这个函数可以看到口头了__gthread_active_p,咱们继续找__gthread_active_p的成功。
__gthread_active_p是一个内联函数,前往一个整数值。
staticvoidconst__gthread_active_ptr是一个静态指针常量,初始化为__gthrw___pthread_key_create函数的地址。
extension是一个GNUC裁减,用于告知编译器防止对某些表白式启动正告。在此处,它将地址转换为void类型,以防止类型不婚配的正告。
&__gthrw___pthread_key_create或许是一个特定线程库(如POSIX线程库)外部的函数,用于创立线程特定数据的关键字。
函数前往__gthread_active_ptr!=0,即假设该指针非空,则标明线程已激活(从指针命名上猜的)。
所以该函数用于批示线程能否被激活。不明确?咱们继续看__gthrw___pthread_key_create的定义。
11405static__typeof(pthread_key_create)__gthrw___pthread_key_create__attribute__((__weakref__("__pthread_key_create")));
经过attribute((weakref("__pthread_key_create"))),将__gthrw___pthread_key_create弱援用到__pthread_key_create。
弱援用是一种机制,准许在链接环节中,假设存在__pthread_key_create的定义,则经常使用它。但假设找不到__pthread_key_create,则准许__gthrw___pthread_key_create依然存在,只不过它将坚持为空或未定义形态。
这里大抵就明确了,总结一下,call_once外部成功中要找一个__pthread_key_create定义,假设不存在则前往空,即咱们调试的时的指针给了0,从而惹起意外。__pthread_key_create函数很显著是线程函数。好,那咱们在代码中加上该函数调用试试看。在main开局参与如下:
intmain(){pthread_key_tkey;pthread_key_create(&key,NULL);Init();
编译运转:
到达预期成果了!
所以全体剖析一下,在call_once的外部成功中,检测能否支持__pthread_key_create或许是为了确保在经常使用call_once启动线程同步时,能够应用线程特定数据键来治理形态或资源,确保其正确性和性能。假设以后环境不支持__pthread_key_create,那么在多线程环境下或许无法有效地治理线程特定的形态信息。
因此,检测能否支持__pthread_key_create或许是call_once成功中的一种战略,用于在或许的状况下提供更好的线程安保性和性能。假设以后环境不支持这个特定的配置,或许会驳回其余形式来成功call_once,或许简化其行为以确保程序在这样的环境中仍能正确运转,虽然或许会就义一些特定的配置或性能。
我尝试不经常使用__pthread_key_create,随意调用一个pthread库的api,比如pthread_create,或许pthread_mutex_init,调用可以所有传递空指针,结果依然是可以到达预期的。所以验证也说明call_once外部经过弱援用库函数来检测以后能否支持多线程,假设不支持则抛出意外,所以经常使用前提必定是多线程环境。
总结
call_once的魅力与留意事项:
std::call_once提供了一种方便而又弱小的多线程同步形式,但在经常使用时也需留意一些细节。比如必定要确保程序是多线程调用,假设有多线程人造还要确保线程安保,防止潜在的死锁和竞态条件疑问登。
回调函数(callback)是什么? ,,
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python、ECMAScript等更现代的编程语言中还可以使用仿函数或匿名函数。
回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。
最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。
意义
因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
C语言中函数调用问题
如果一个函数要使用参数,它就必须定义接受参数值的变量。 形式参数与实际参数 函数定义时填入的参数我们称之为形式参数,简称形参,它们同函数内部的局部变量作用相同。 形参的定义是在函数名之后和函数开始的花括号之前。 调用时填入的参数,我们称之为实际参数,简称实参。 必须确认所定义的形参与调用函数的实际参数类型一致,同时还要保证在调用时形参与实参的个数出现的次序也要一一对应。 如果不一致,将产生意料不到的结果。 与许多其它高级语言不同,(是健壮的,它总要做一些甚至你不希望的事情,几乎没有运行时错误检查,完 全没有范围检测。 作为程序员,必须小心行事以保证不发生错误,安全运行。 赋值调用与引用调用 一般说来,有两种方法可以把参数传递给函数。 第一种叫做“赋值调用”(callbyvalue),这种方法是把参数的值复制到函数的形式参数中。 这样,函数中的形式参数的任何变化不会影响到调用时所使用的变量。 把参数传递给函数的第二种方法是“引用调用”(callbyreference)。 这种方法是把参数的地址复制给形式参数,在函数中,这个地址用来访问调用中所使用的实际参数。 这意味着,形式参数的变化会影响调用时所使用的那个变量
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。