@ -1,34 +0,0 @@ | |||
# log | |||
由于部分nif依赖于c99编译,而部分环境gcc版本低于依赖导致编译不过,特此记录Gcc升级命令 | |||
# 步骤 | |||
1. 安装centos-release-scl | |||
sudo yum install centos-release-scl | |||
2. 查看最新的版本并安装 | |||
yum list | grep devtoolset 然后选择你想要安装的版本,比如: | |||
sudo yum install devtoolset-9-gcc* | |||
3. 激活对应的devtoolset,所以你可以一次安装多个版本的devtoolset,需要的时候用下面这条命令切换到对应的版本 | |||
scl enable devtoolset-9 bash | |||
gcc -v | |||
注意:::这条激活命令只对本次会话有效,重启会话后还是会变回原来的版本 想随意切换可按如下操作。 | |||
首先,安装的devtoolset是在 /opt/sh 目录下的,如图 | |||
每个版本的目录下面都有个 enable 文件,如果需要启用某个版本,只需要执行 | |||
source ./enable | |||
所以要想切换到某个版本,只需要执行 | |||
source /opt/rh/devtoolset-8/enable | |||
可以将对应版本的切换命令写个shell文件放在配了环境变量的目录下,需要时随时切换,或者开机自启 | |||
4. 直接替换旧的gcc | |||
旧的gcc是运行的 /usr/bin/gcc,所以将该目录下的gcc/g++替换为刚安装的新版本gcc软连接,免得每次enable | |||
复制代码 | |||
mv /usr/bin/gcc /usr/bin/gcc-4.8.5 | |||
ln -s /opt/rh/devtoolset-8/root/bin/gcc /usr/bin/gcc | |||
mv /usr/bin/g++ /usr/bin/g++-4.8.5 | |||
ln -s /opt/rh/devtoolset-8/root/bin/g++ /usr/bin/g++ | |||
gcc --version | |||
g++ --version | |||
@ -1,88 +0,0 @@ | |||
文件命名为 | |||
application_Name.app | |||
格式如下: | |||
{application,"app名字", | |||
[ | |||
{description,"app描述"}, | |||
{vsn ,"版本号"}, | |||
{id ,Id},%%app id 同 erl -id ID | |||
{modules,[Modules]},%%app包含的模块,systools模块使用它来生成script、tar文件 | |||
{maxP,Num},%%进程最大值 | |||
{maxT,Time},%%app运行时间 单位毫秒 | |||
{registered,[mod]},%%指定app 名字模块,systools用来解决名字冲突 | |||
{included_applictions ,[XX]},%%指定子 app,只加载,但是不启动 | |||
{applictions,[xxxx]},%%启动自己的app前,appliation:ensure_all_started将会首先启动此列表的app application:start会检查该列表是否都启动 | |||
{env,[xxxx]},%%配置app的env,可以使用application:get_env(AppName, Key)获取 | |||
{mod,{xxx,args}},%%指定app启动模块,参数,对应自己app的application behavior | |||
{start_phases,[{xxx,xxx}]]%%指定启动阶段一些操作,对应otp application start_phase函数 | |||
] | |||
} | |||
必须要配置的为description,vsn,modules,registered,applications。 | |||
Application为应用名, | |||
descripttion为应用的简单描述 | |||
id 产品标识 | |||
vsn 应用版本 | |||
modules 应用所涉及到的module | |||
registered 注册进程 | |||
applications 本应用启动时需要事先启动的其他应用 | |||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |||
erlang官网说明 | |||
{application, Application, | |||
[{description, Description}, | |||
{id, Id}, | |||
{vsn, Vsn}, | |||
{modules, Modules}, 本应用程序引入的所有模块。systools 在生成启动脚本和tar文件时使用这个列表。一个模块只能在一个应用程序中定义 | |||
{maxP, MaxP}, 已经弃用了 | |||
{maxT, MaxT}, 时间单位为毫秒 | |||
{registered, Names}, 注册过程的所有名称均在此应用程序中启动。systools使用这个列表来检测不同应用程序之间的名字冲突 | |||
{included_applications, Apps}, 此应用程序包含的所有应用程序 当此应用程序启动时,应用程序控制器会自动加载所有包含的应用程序,但不会启动。假设包含应用程序的最高管理者由本应用程序的主管启动 | |||
{applications, Apps}, 允许启动此应用程序之前必须启动的所有应用程序。systools使用这个列表来生成正确的启动脚本。缺省为空列表,但请注意所有应用程序对(至少)Kernel和STDLIB都有依赖关系 | |||
{env, Env}, 应用程序使用的配置参数。通过调用application:get_env / 1,2来检索配置参数的值 | |||
{mod, Start}, 指定应用程序回调模块和启动参数 | |||
对于作为监督树实施的应用程序,密钥mod是必需的,否则应用程序控制器不知道如何启动它。 对于没有进程的应用程序(通常是代码库,例如STDLIB),可以省略mod | |||
{start_phases, Phases}, | |||
{runtime_dependencies, RTDeps}]}. 应用程序依赖的应用程序版本列表 | |||
Value Default | |||
----- ------- | |||
Application atom() - | |||
Description string() "" | |||
Id string() "" | |||
Vsn string() "" | |||
Modules [Module] [] | |||
MaxP int() infinity | |||
MaxT int() infinity | |||
Names [Name] [] | |||
Apps [App] [] | |||
Env [{Par,Val}] [] | |||
Start {Module,StartArgs} [] | |||
Phases [{Phase,PhaseArgs}] undefined | |||
RTDeps [ApplicationVersion] [] | |||
Module = Name = App = Par = Phase = atom() | |||
Val = StartArgs = PhaseArgs = term() | |||
ApplicationVersion = string() | |||
如果要使用systools中的函数 需要设置下面的key参数 | |||
description vsn modules registered applications | |||
其他的key被systools忽略 | |||
应用的策略 | |||
application:start(Name, Type) | |||
type: | |||
• permanent: if the app terminates, the entire system is taken down, excluding manual termination of the app with application:stop/1. | |||
• transient: if the app terminates for reason normal, that’s ok. Any other reason for termination shuts down the entire system. | |||
• temporary: the application is allowed to stop for any reason. It will be reported, but nothing bad will happen. | |||
@ -1,64 +0,0 @@ | |||
# 描述 | |||
NIF库包含Erlang模块的某些功能的本机实现。像其他任何函数一样,调用本机实现的函数(NIF),与调用方没有任何区别。 | |||
NIF库被构建为动态链接的库文件,并通过调用erlang:load_nif / 2在运行时加载。 | |||
警告 | |||
谨慎使用此功能。 | |||
执行本机功能作为VM的本机代码的直接扩展。执行不是在安全的环境中进行的。VM 无法提供与执行Erlang代码时相同的服务, | |||
例如抢先式调度或内存保护。如果本机功能运行不正常,则整个VM都会出现异常。 | |||
崩溃的本机功能将使整个VM崩溃。 | |||
错误实现的本机功能可能会导致VM内部状态不一致,从而导致VM崩溃或在调用本机功能后的任何时候VM的其他异常行为。 | |||
在返回之前进行长时间工作的本机功能会降低VM的响应能力,并可能导致其他奇怪的行为。这种奇怪的行为包括但不限于极端的内存 | |||
使用情况以及调度程序之间的不良负载平衡。在Erlang / OTP发行版之间,由于冗长的工作而可能发生的奇怪行为也会有所不同。 | |||
# 简单示例 | |||
``` | |||
/* niftest.c */ | |||
#include <erl_nif.h> | |||
static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||
{ | |||
return enif_make_string(env, "Hello world!", ERL_NIF_LATIN1); | |||
} | |||
static ErlNifFunc nif_funcs[] = | |||
{ | |||
{"hello", 0, hello} | |||
}; | |||
ERL_NIF_INIT(niftest,nif_funcs,NULL,NULL,NULL,NULL) | |||
``` | |||
``` | |||
-module(niftest). | |||
-export([init/0, hello/0]). | |||
-on_load(init/0). | |||
init() -> | |||
erlang:load_nif("./niftest", 0). | |||
hello() -> | |||
erlang:nif_error("NIF library not loaded"). | |||
``` | |||
在上面的示例中,使用了on_load指令,该命令功能是在加载模块时自动调用的指定的函数-init/0。 | |||
init函数初始化依次调用erlang:load_nif / 2 , | |||
该加载器会加载NIF库,并用C中的本机实现替换hello函数。加载后,NIF库将保持不变。在清除它所属的模块代码版本之前,不会将其卸载。 | |||
如果在成功加载NIF库之前调用了该函数,则每个NIF必须具有用Erlang调用的实现。典型的此类存根实现是调用erlang:nif_error, | |||
这将引发异常。如果NIF库缺少某些操作系统或硬件体系结构的实现,则Erlang函数也可以用作后备实现。 | |||
注意 | |||
NIF不必导出,它可以在模块本地。但是,编译器会优化未使用的本地存根函数,从而导致NIF库的加载失败。 | |||
# 功能性 | |||
NIF代码和Erlang运行时系统之间的所有交互都是通过调用NIF API函数来执行的。存在以下功能的功能: | |||
读写Erlang术语 | |||
任何Erlang术语都可以作为函数参数传递给NIF,并作为函数返回值返回。这些术语属于C类型的 ERL_NIF_TERM,只能使用API函数读取或写入。大部分用于读取术语内容的函数都以enif_get_为前缀,并且如果该术语属于预期类型(或非预期类型),则通常返回 true(或false)。编写术语的函数都带有enif_make_前缀 ,通常返回创建的ERL_NIF_TERM。还有一些查询术语的函数,例如enif_is_atom,enif_is_identical和enif_compare。 | |||
类型的所有方面ERL_NIF_TERM属于类型的环境ErlNifEnv。术语的生存期由其环境对象的生存期控制。读取或写入术语的所有API函数都将术语所属的环境作为第一个函数参数 | |||
增加和减少资源的引用计数的次数必须匹配,否则可能引发问题。 | |||
至此,持久资源的主要接口的实现就介绍完了,用户使用时,可以先通过enif_open_resource_type建立资源类型的描述符, | |||
然后利用此描述符,使用enif_alloc_resource分配资源所占用的内存空间,使用enif_make_resource将资源导出到erlang模块层, | |||
在进程间传递资源描述符,资源再传回NIF时,可以通过enif_get_resource取回资源描述符中的资源数据结构, | |||
同时可以通过enif_keep_resource来共享资源,通过enif_release_resource来放弃使用资源,gc系统也会正确回收引用计数为0的资源, | |||
开发者再也不用担心内存没有被正确释放了。 | |||
持久资源为NIF的开发带来了极大的便利,用户可以将一些大规模的数据结构一次传入内存,生成一个资源描述符, | |||
然后在进程间传递资源描述符而不是资源数据本身,减轻每次资源数据拷贝的开销,同时持久资源也是线程安全的, | |||
写erlang程序也可以像写c程序一样高效了。 | |||
@ -1,243 +0,0 @@ | |||
### 监督原则 | |||
主管负责启动,停止和监视其子进程。主管的基本思想是,必须通过在必要时重新启动子进程来保持其子进程活动。 | |||
主管的孩子被定义为孩子规格列表 。当主管启动时,将根据此列表从左到右依次启动子进程。主管终止时,它首先以相反的启动顺序从右到左终止其子进程。 | |||
sup_flags() = | |||
#{strategy => strategy(), % optional | |||
intensity => non_neg_integer(), % optional | |||
period => pos_integer() % optional | |||
} | |||
#### 重启策略 | |||
one_for_one-如果一个子进程终止并要重新启动,则仅影响该子进程。这是默认的重启策略。 | |||
one_for_all-如果一个子进程终止并要重新启动,则所有其他子进程均终止,然后重新启动所有子进程。 | |||
rest_for_one-如果一个子进程终止并要重新启动,则子进程的“剩余”(即,按照启动顺序终止的子进程之后的子进程)将终止。 | |||
然后,终止的子进程以及重新启动后的所有子进程。 | |||
simple_one_for_one-简化的one_for_one 主管,其中所有子进程都是动态添加的,具有相同进程类型(即,运行相同代码)的实例。 | |||
功能 delete_child / 2和 restart_child / 2 是无效simple_one_for_one监事和回报 {错误,simple_one_for_one}如果指定的主管使用此重启策略。 | |||
通过将子级的pid()指定为第二个参数,可以将函数terminate_child / 2用于simple_one_for_one主管下的子级。 | |||
如果改用子规范标识符,则 terminate_child / 2返回 {error,simple_one_for_one}。 | |||
由于simple_one_for_one主管可以有多个子代,因此它会异步关闭所有子代。这意味着孩子们并行进行清理,因此未定义他们停止的顺序 | |||
为了防止主管进入子进程终止和重新启动的无限循环,使用上面映射中的键强度和周期指定的两个整数值来定义最大重新启动强度。 | |||
假设值MAXR为强度 和MAXT为周期,然后,如果超过MAXR 重新启动内发生MAXT秒,监控终止所有的子进程,然后本身。在这种情况下, | |||
主管本身的终止原因将被关闭。 强度默认为1,期间默认为 5。 | |||
#### 子进程规范 | |||
child_spec() = | |||
#{ | |||
id => child_id(), % mandatory | |||
start => mfargs(), % mandatory | |||
restart => restart(), % optional | |||
shutdown => shutdown(), % optional | |||
type => worker(), % optional | |||
modules => modules() % optional | |||
} | |||
id用于由主管内部标识子规范。该ID关键是强制性的。 | |||
请注意,这个在职业上的标识符被称为“名称”。现在尽可能使用术语“标识符”或“ id”,但为了保持向后兼容性,仍然可以找到“名称”的某些出现,例如在错误消息中。 | |||
start定义用于启动子进程的函数调用。它必须是用作apply(M,F,A)的模块功能参数元组{M,F,A}。 | |||
启动函数必须创建并链接到子进程,并且必须返回{ok,Child}或 {ok,Child,Info},其中Child是子进程的pid,Info是主管忽略的任何术语。 | |||
restart定义终止的子进程何时必须重新启动。 | |||
一个permanent 的子进程总是会重启。 | |||
一个temporary 的子进程不会再重新启动(甚至当主管的重启策略是rest_for_one或one_for_all和兄弟姐妹的死亡原因临时进程被终止)。 | |||
一个transient的子进程重新启动,只有当它异常终止,即与另一个出口原因,而不是 normal,shutdown,或{shutdown,Term}。 | |||
该restart是可选的。如果未指定,则默认为permanent.。 | |||
shutdown定义必须终止子进程的方式。 | |||
brutal_kill意味着子进程使用exit(Child,kill)无条件终止 | |||
整数超时值表示主管通过调用exit(Child,shutdown)告诉子进程终止 ,然后等待退出信号,原因是该子进程退出了shutdown。如果在指定的毫秒数内未收到退出信号,则使用exit(Child,kill)无条件终止子进程 。 | |||
infinity 如果子进程是另一个主管,则必须将关闭时间设置为infinity,以使子树有足够的时间关闭。 | |||
对于类型为Supervisor的孩子,将关闭时间设置为无穷大以外的任何时间,都 可能导致比赛状态,在该情况下,所讨论的孩子会取消其自己的孩子的链接,但无法在杀死孩子之前终止他们。 | |||
如果子进程是工作进程,也可以将其设置为infinity。 | |||
当子进程为工作进程时,将关闭时间设置为无穷大时要小心 。因为在这种情况下,监视树的终止取决于子进程,所以它必须以安全的方式实现,并且其清除过程必须始终返回。 | |||
在shutdown 是可选的。如果未指定,则在子类型为worker的情况下默认为5000,在子类型为supervisor的情况下默认为infinity。 | |||
type specifies if the child process is a supervisor or a worker. | |||
该type关键是可选的。如果未指定,则默认为worker。. | |||
modules 在代码替换期间,释放处理程序将使用modules模块来确定哪些进程正在使用某个模块。作为一个经验法则,如果孩子过程是一个 主管,gen_server或, | |||
gen_statem,这是为与一个元素列表[模块],其中模块是回调模块。如果子进程是具有动态回调模块集的事件管理器(gen_event),则 必须使用动态值。 | |||
有关发布处理的更多信息,请参见 OTP设计原则中的发布处理。 | |||
该modules的关键是可选的。如果未指定,则默认为[M],其中M来自孩子的开头{M,F,A}。 | |||
Internally 主管还跟踪PID的 孩子的孩子的过程中,或者不确定如果没有PID存在。 | |||
#### 函数 | |||
count_children(SupRef) -> PropListOfCounts | |||
Types SupRef = sup_ref() | |||
PropListOfCounts = [Count] | |||
Count = | |||
{specs, ChildSpecCount :: integer() >= 0} | 已死或活着的孩子总数。 | |||
{active, ActiveProcessCount :: integer() >= 0} | 此主管管理的所有正在运行的子进程的计数 | |||
对于 simple_one_for_one主管,不会执行任何检查以确保每个子进程仍处于活动状态,尽管除非主管非常重载,否则此处提供的结果可能非常准确。 | |||
{supervisors, ChildSupervisorCount :: integer() >= 0} | 规范列表中标记为child_type =主管的所有子进程的计数 ,无论该子进程是否仍在运行 | |||
{workers, ChildWorkerCount :: integer() >= 0} -标记为所有儿童的数量 CHILD_TYPE =工人在规范列表中,如果不管孩子进程仍然活着。 | |||
delete_child(SupRef, Id) -> Result | |||
Types SupRef = sup_ref() | |||
Id = child_id() | |||
Result = ok | {error, Error} | |||
Error = running | restarting | not_found | simple_one_for_one | |||
告诉主管SupRef删除Id标识的子规范。相应的子进程一定不能运行。使用 terminate_child / 2终止它。 | |||
如果成功,函数将返回ok。如果存在由ID标识的子规范,但相应的子进程正在运行或将要重新启动,则该函数分别返回{error,running}或 {error,restarting}。如果由ID标识的子规范不存在,则该函数返回{error,not_found}。 | |||
get_childspec(SupRef, Id) -> Result | |||
Types | |||
SupRef = sup_ref() | |||
Id = pid() | child_id() | |||
Result = {ok, child_spec()} | {error, Error} | |||
Error = not_found | |||
返回由Id在主管SupRef下标识的子项的子项规范图。返回的映射包含所有键,包括必需键和可选键。 | |||
restart_child(SupRef, Id) -> Result | |||
Types | |||
SupRef = sup_ref() | |||
Id = child_id() | |||
Result = | |||
{ok, Child :: child()} | | |||
{ok, Child :: child(), Info :: term()} | | |||
{error, Error} | |||
Error = | |||
running | restarting | not_found | simple_one_for_one | term() | |||
告诉主管SupRef重新启动与Id标识的子规范相对应的子进程。子规范必须存在,并且相应的子进程一定不能运行。 | |||
注意,对于临时子代,子代说明在子代终止时会自动删除;因此,不可能重新启动此类子级。 | |||
如果由ID标识的子规范不存在,则该函数返回{error,not_found}。如果子规范存在但相应的进程已在运行,则该函数返回{error,running}。 | |||
如果子进程启动函数返回{ok,Child} 或{ok,Child,Info},则将pid添加到主管,并且该函数返回相同的值。 | |||
如果子进程启动函数返回ignore,则pid保持设置为undefined,该函数返回{ok,undefined}。 | |||
如果子进程启动函数返回错误元组或错误值,或者失败,则该函数返回 {error,Error},其中Error是包含有关错误信息的术语。 | |||
start_child(SupRef, ChildSpec) -> startchild_ret() | |||
Types | |||
SupRef = sup_ref() | |||
ChildSpec = child_spec() | (List :: [term()]) | |||
startchild_ret() = | |||
{ok, Child :: child()} | | |||
{ok, Child :: child(), Info :: term()} | | |||
{error, startchild_err()} | |||
startchild_err() = | |||
already_present | {already_started, Child :: child()} | term() | |||
ChildSpec必须是有效的子规范(除非主管是simple_one_for_one 主管;请参见下文)。通过使用子规范中定义的启动功能来启动子进程。 | |||
对于simple_one_for_one主管,将使用Module:init / 1中定义的子规范,而ChildSpec必须改为是List的任意列表。然后,通过将List附加到现有的启动函数参数(即,通过调用apply(M,F,A ++ List))来 启动子进程,其中{M,F,A}是子规范中定义的启动函数。 | |||
如果已经存在带有指定标识符的子规范,则将丢弃ChildSpec,并且该函数将根据相应的子进程是否在运行而返回{error,already_present}或 {error,{already_started,Child}}。 | |||
如果子进程启动函数返回 {ok,Child}或 {ok,Child,Info},则将子规范和pid添加到主管,并且该函数返回相同的值。 | |||
如果子进程启动函数返回ignore,则将子规范添加到主管(除非该主管是simple_one_for_one主管,请参见下文),将pid设置为undefined,并且该函数返回 {ok,undefined}。 | |||
对于simple_one_for_one主管,当子进程启动函数返回ignore时,该函数将返回 {ok,undefined},并且没有子级添加到主管。 | |||
如果子进程启动函数返回错误元组或错误值,或者失败,则子规范被丢弃,函数返回{error,Error},其中 Error是包含有关错误和子规范的信息的术语。 | |||
start_link(模块,Args)-> startlink_ret() | |||
start_link(SupName,Module,Args)-> startlink_ret() | |||
种类 | |||
SupName = sup_name() | |||
模块= module() | |||
Args = term() | |||
startlink_ret()= | |||
{确定,pid()} | 忽略| {错误,startlink_err()} | |||
startlink_err()= | |||
{已经开始,pid()} | {关机,term()} | 术语() | |||
sup_name()= | |||
{本地,名称:: atom()} | | |||
{global,Name :: atom()} | | |||
{via,Module :: module(),Name :: any()} | |||
创建一个监督程序,作为监督树的一部分。例如,该功能可确保主管链接到呼叫过程(其主管)。 | |||
创建的主管进程将调用 Module:init / 1来查找有关重启策略,最大重启强度和子进程的信息。为了确保同步启动过程,在返回Module:init / 1并启动所有子进程之前,不会返回 start_link / 2,3。 | |||
如果SupName = {local,Name},则主管使用register / 2在本地注册为Name。 | |||
如果SupName = {global,Name},则使用 global:register_name / 2将主管全局注册为Name。 | |||
如果 SupName = {via,Module,Name},则使用Module表示的注册表将主管注册为Name。所述模块的回调必须导出功能REGISTER_NAME / 2, unregister_name / 1,和发送/ 2,它必须表现得像在相应的功能 全球。因此, {via,global,Name}是有效的引用。 | |||
如果未提供姓名,则主管未注册。 | |||
模块是回调模块的名称。 | |||
Args是作为参数传递给Module:init / 1的任何术语。 | |||
如果成功创建了主管及其子进程(即,如果所有子进程启动函数都返回 {ok,Child},{ok,Child,Info}或ignore),则该函数返回{ok,Pid},其中Pid是主管的pid。 | |||
如果已经存在具有指定SupName的进程,则 该函数返回 {error,{already_started,Pid}},其中Pid是该进程的pid。 | |||
如果Module:init / 1返回ignore,则此函数也返回ignore,并且supervisor因normal终止。 | |||
如果Module:init / 1失败或返回错误值,则此函数返回{error,Term},其中 Term是包含有关错误信息的术语,而主管则以Term终止。 | |||
如果任何子进程启动功能失败或返回错误的元组或错误值,则主管首先使用原因shutdown终止所有已启动的子进程, 然后终止自身并返回 {error,{shutdown,Reason}}。 | |||
Terminate_child(SupRef,Id)->结果 | |||
种类 | |||
SupRef = sup_ref() | |||
ID = pid()| child_id() | |||
结果=正常| {错误,错误} | |||
错误=找不到| simple_one_for_one | |||
告诉主管SupRef终止指定的孩子。 | |||
如果主管不是simple_one_for_one,则 ID必须是子规范标识符。该过程(如果有的话)将终止,并且除非它是一个临时子进程,否则该子进程规范将由主管保留。子进程以后可以由主管重新启动。子进程也可以通过调用restart_child / 2显式重启 。使用 delete_child / 2 删除子级规范。 | |||
如果子级是临时的,则该子级规范将在过程终止后立即删除。这意味着delete_child / 2没有意义,并且restart_child / 2不能用于这些子级。 | |||
如果主管是simple_one_for_one,则 ID 必须是子进程的pid()。如果指定的进程处于活动状态,但不是指定的主管的子进程,则该函数返回 {error,not_found}。如果指定了子规范标识符而不是pid(),则函数返回{error,simple_one_for_one}。 | |||
如果成功,函数将返回ok。如果没有带有指定ID的子规范,则该函数返回{error,not_found}。 | |||
有关SupRef的描述,请参见 start_child / 2。 | |||
which_children(SupRef)-> [{Id,Child,Type,Modules}] | |||
种类 | |||
SupRef = sup_ref() | |||
ID = child_id() | 未定义 | |||
子= 子() | 重新开始 | |||
类型= worker() | |||
模块= modules() | |||
返回一个新创建的列表,其中包含有关属于主管SupRef的所有子规范和子进程的信息。 | |||
请注意,在内存不足的情况下监视多个子项时调用此函数可能会导致内存不足异常。 | |||
有关SupRef的描述,请参见 start_child / 2。 | |||
为每个子规范/过程提供以下信息: | |||
Id-在子规范中定义,或 为simple_one_for_one主管未定义。 | |||
子 -相应子进程的pid,如果该进程将要重新启动,则原子重新启动;如果没有这样的进程,则未定义。 | |||
类型 -子规范中定义的类型。 | |||
模块 -子规范中定义的模块。 | |||
Module:init(Args)->结果 | |||
种类 | |||
Args = term() | |||
结果= {确定,{SupFlags,[ChildSpec]}} | 忽视 | |||
SupFlags = sup_flags() | |||
ChildSpec = child_spec() | |||
每当使用start_link / 2,3启动管理员时 ,新进程就会调用此函数以查找有关重新启动策略,最大重新启动强度和子级规范的信息。 | |||
Args是提供给start函数的Args参数。 | |||
SupFlags是主管标志,用于定义主管的重新启动策略和最大重新启动强度。[ChildSpec]是有效的子规范的列表,这些规范定义了主管必须启动和监视的子进程。请参阅前面的“ 监督原则”一节中的讨论 。 | |||
请注意,当重新启动策略为 simple_one_for_one时,子规范的列表必须是仅包含一个子规范的列表。(忽略子规范标识符。)然后,在初始化阶段不启动任何子进程,但是假定所有子进程都使用start_child / 2动态启动 。 | |||
该函数还可以返回ignore。 | |||
请注意,此功能也可以作为代码升级过程的一部分来调用。因此,该功能不应有任何副作用。有关管理程序代码升级的更多信息,请参阅《OTP设计原则》中的“ 更改管理程序”部分 。 | |||
@ -1,35 +0,0 @@ | |||
# window下编译nif dll 需要安装vs | |||
## 第一种 makefile配置(可参考jiffy的Makefile) 命令行下编译dll 需要设置vs相关环境变量 | |||
具体要设置的环境变量可参考下面几个 | |||
``` | |||
path 新增 | |||
D:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.24.28314\bin\Hostx64\x64 | |||
LIB | |||
D:\Windows Kits\10\Lib\10.0.18362.0\ucrt\x64 | |||
D:\Windows Kits\10\Lib\10.0.18362.0\um\x64 | |||
D:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.24.28314\lib\x64 | |||
INCLUDE | |||
D:\Windows Kits\10\Include\10.0.18362.0\ucrt | |||
D:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.24.28314\include | |||
``` | |||
### 脚本配置 | |||
VsDevCmd.bat 文件设置适当的环境变量来生成命令行。 | |||
备注 | |||
Visual Studio 2015 及更早版本基于相同目的使用 VSVARS32.bat,而不是 VsDevCmd.bat。 | |||
此文件保存在 | |||
Program Files\Microsoft Visual Studio\Version \Common7\Tools 或 | |||
Program Files (x86)\Microsoft Visual Studio\Version \Common7\Tools。 | |||
## 第二种 在vs单独编译 然后拷贝使用 | |||
VS编译 | |||
1 新建空项目或者从现有代码创建项目 | |||
2 先选择 编辑框上边的 解决方案配置 与 解决方案平台 | |||
3 右键项目属性 设置 配置与第2步 解决方案配置 一样 设置 平台与第二步设置的 解决方案平台 一样 | |||
4 右键项目属性 配置属性 -> 常规 -> 配置类型 ->动态库(.dll) | |||
5 右键项目属性 配置属性 -> VC++目录 -> 包含目录 新增 D:\Program Files\erl10.6\erts-10.6\include | |||
6 右键项目属性 生成 | |||
注意编译使用的erlang include要合使用的erl版本对应 |
@ -1,95 +0,0 @@ | |||
## 二进制语法 | |||
Bin = <<E1, E2, ... En>> | |||
<<E1, E2, ... En>> = Bin | |||
每个E 1..n 指定bitstring的Segment | |||
每个段具有以下一般语法: | |||
Value:Size/TypeSpecifierList | |||
可以省略Size或TypeSpecifier或两者。因此,允许以下变体: | |||
Ei = | |||
Value | | |||
Value:Size | | |||
Value/TypeSpecifierList | | |||
Value:Size/TypeSpecifierList | |||
### Value | |||
当在二进制构造中使用时,Value部分是任何表达式,用于求值为整数,浮点或位串,如果表达式不是单个文字或变量,则将其括在括号中。 | |||
在二进制匹配中使用时,用于位串匹配,Value必须是变量,或整数,浮点或字符串,简单而言就是Value部分必须是文字或变量。 | |||
### Size | |||
在位串构造中使用,Size是要求求整数的表达式。 | |||
用于位串匹配,Size必须是整数,或绑定到整数的变量。 | |||
Size的值以Unit指定段的大小,默认值取决于类型 | |||
• For integer it is 8. | |||
• For float it is 64. | |||
• For binary and bitstring it is the whole binary or bit string. | |||
在匹配中,此默认值仅对最后一个元素有效。匹配中的所有其他位串或二进制元素必须具有大小规范,段的大小Size部分乘以TypeSpecifierList中的unit(稍后描述)给出了段的位数.对于utf8,utf16和utf32类型,不得给出Size的大小。段的大小由类型和值本身隐式确定。 | |||
### TypeSpecifierList | |||
是一个类型说明符列表,按任何顺序,用连字符("-")分隔。默认值用于任何省略的类型说明符 | |||
#### Type | |||
Type= integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32 | |||
默认值为integer。bytes是二进制的简写,bits是bitstring的简写。有关utf类型的更多信息,请参见下文。 | |||
#### Signedness | |||
Signedness= signed | unsigned | |||
只有匹配和类型为整数时才有意义。默认值为无符号。 | |||
#### Endianness | |||
Endianness= big | little | native | |||
Native-endian意味着字节顺序在加载时被解析为big-endian或little-endian,具体取决于运行Erlang机器的CPU的本机内容。仅当Type为integer,utf16,utf32或float时,字节顺序才有意义。默认值为big。 | |||
#### Unit | |||
Unit= unit:IntegerLiteral | |||
允许的范围是1..256。对于integer,float和bitstring,默认值为1;对于binary,默认值为8。对于utf8,utf16和utf32类型,不能给出Unit说明符。 | |||
它与Size说明符相乘,以给出段的有效大小。单位大小指定没有大小的二进制段的对齐方式,二进制类型的段必须具有可被8整除的大小 | |||
### 注意 | |||
构造二进制文件时,如果整数段的大小N太小而不能包含给定的整数,则整数的最高有效位将被静默丢弃,并且只有N个最低有效位被放入二进制。 | |||
#### 例子: | |||
X:4/little-signed-integer-unit:8 | |||
该元素的总大小为4 * 8 = 32位,它包含一个小端序的有符号整数 | |||
### 关于 utf8 utf16 utf32 | |||
构造utf类型的段时,Value必须是0..16#D7FF或16#E000 .... 16#10FFFF范围内的整数。如果Value超出允许范围,则构造将失败并返回badarg异常。生成的二进制段的大小取决于类型或值,或两者: | |||
• For utf8, Value is encoded in 1-4 bytes. | |||
• For utf16, Value is encoded in 2 or 4 bytes. | |||
• For utf32, Value is always be encoded in 4 bytes. | |||
构造时,可以给出一个文字字符串,后跟一个UTF类型,例如:<<“abc”/ utf8 >>,这是<< $ a / utf8,$ b / utf8,$ c / utf8的语法糖>>。 | |||
成功匹配utf类型的段,得到0..16#D7FF或16#E000..16#10FFFF范围内的整数。 | |||
如果返回值超出这些范围,则匹配失败。 | |||
### 如何实现二进制文件 | |||
在内部,二进制和位串以相同的方式实现。 | |||
内部有四种类型的二进制对象: | |||
两个是二进制数据的容器,称为: | |||
• Refc binaries (short for reference-counted binaries) | |||
• Heap binaries | |||
两个仅仅是对二进制文件的一部分的引用,被称为: | |||
• sub binaries | |||
• match contexts | |||
### Refc Binaries | |||
Refc二进制文件由两部分组成: | |||
•存储在进程堆上的对象,称为ProcBin | |||
•二进制对象本身,存储在所有进程堆之外 | |||
任何数量的进程都可以通过任意数量的ProcBins引用二进制对象。该对象包含一个引用计数器,用于跟踪引用的数量,以便在最后一个引用消失时将其删除。 | |||
进程中的所有ProcBin对象都是链表的一部分,因此当ProcBin消失时,垃圾收集器可以跟踪它们并减少二进制文件中的引用计数器。 | |||
### Heap Binaries | |||
堆二进制文件是小型二进制文件,最多64个字节,并直接存储在进程堆上。它们在进程被垃圾收集时以及作为消息发送时被复制。它们不需要垃圾收集器进行任何特殊处理。 | |||
### Sub Binaries | |||
The reference objects sub binaries and match contexts can reference part of a refc binary or heap binary | |||
子二进制文件由split_binary / 2创建或者当二进制文件以二进制模式匹配时。子二进制是对另一个二进制文件(refc或堆二进制文件的一部分,但从不进入另一个子二进制文件)的引用。因此,匹配二进制文件相对便宜,因为实际的二进制数据永远不会被复制。 | |||
### Match Context | |||
匹配上下文类似于子二进制,但针对二进制匹配进行了优化 | |||
### 关于iolist | |||
定义(直接引用霸爷的文章) | |||
1. [] | |||
2. binary | |||
3. 列表, 每个元素是int(0-255)或者binary或者iolist. | |||
其中binary是指 bitsize % 8 == 0 . | |||
int 是0-255 | |||
Iolist的作用是用于往port送数据的时候.由于底层的系统调用如writev支持向量写, 就避免了无谓的iolist_to_binary这样的扁平话操作, 避免了内存拷贝,极大的提高了效率. | |||
另外额外补充: | |||
erlang中列表时在头部添加比较高效,但是binary是在尾部追加更高效 | |||
### 关于消息接收转发解码和发送 | |||
erlang通常会将接收到的消息由网关进程转发给其他工作进程, 建议先匹配消息id, 然后转发二进制消息到工作进程,然后由工作进程解码再处理 | |||
同时广播消息可先编码成二进制之后再广播, 避免重复编码 |
@ -1,448 +0,0 @@ | |||
## gen_tcp 编程接口 | |||
#### listen(Port, Options) -> {ok, ListenSocket} | {error, Reason} | |||
Types | |||
Port = inet:port_number() | |||
Options = [listen_option()] | |||
ListenSocket = socket() | |||
Reason = system_limit | inet:posix() | |||
设置一个套接字以侦听本地主机上的端口Port。 | |||
用法: | |||
listen(Port, Options) -> {ok, ListenSocket} | {error, Reason} | |||
在本地开启一个监听某个端口的套接字(socket)。开启成功的话,会返回一个套接字标识符 Socket,其一般会传递给 get_tcp:accept/1 或 get_tcp:accept/2 调用。 | |||
如果参数 Port 为 0,那么底层操作系统将赋值一个可用的端口号,可以使用 inet:port/1 来获取一个 socket 监听的端口。 | |||
连接到IP地址为Address的主机的TCP端口Port上的服务器。参数 地址可以是主机名或IP地址。 | |||
提供以下选项: | |||
{ip, Address} | |||
如果主机有许多网络接口,则此选项指定要使用的接口。 | |||
{ifaddr, Address} | |||
与{ip,Address}相同。如果主机有许多网络接口,则此选项指定要使用的接口。 | |||
{fd, integer() >= 0} | |||
如果以某种方式未使用gen_tcp连接了套接字 ,请使用此选项传递文件描述符。如果将{ip,Address}和/或 {port,port_number()}与该选项结合使用,则 在连接前将fd绑定到指定的接口和端口。如果未指定这些选项,则假定fd已被适当绑定。 | |||
inet | |||
为IPv4设置套接字。 | |||
inet6 | |||
设置用于IPv6的套接字。 | |||
local | |||
设置Unix域套接字。见 inet:local_address() | |||
{port,Port} | |||
指定要使用的本地端口号。 | |||
{tcp_module, module()} | |||
覆盖使用哪个回调模块。默认为 inet_tcp IPv4和inet6_tcp使用IPv6。 | |||
Opt | |||
参见 inet:setopts / 2。 | |||
可以使用send / 2将数据包发送到返回的套接字Socket。 从对等方发送的数据包将作为消息传递: | |||
{tcp, Socket, Data} | |||
如果套接字处于{active,N}模式(有关详细信息,请参见inet:setopts / 2),并且其消息计数器降至0,则将传递以下消息以指示套接字已转换为被动({active,false}) 模式: | |||
{tcp_passive, Socket} | |||
如果套接字已关闭,则会发出以下消息: | |||
{tcp_closed, Socket} | |||
如果套接字上发生错误,则会传递以下消息(除非在套接字的选项列表中指定了{active,false},在这种情况下,可通过调用recv / 2来检索数据包): | |||
{tcp_error, Socket, Reason} | |||
可选的Timeout参数指定超时(以毫秒为单位)。默认为infinity。 | |||
注意::: | |||
请记住,如果底层OS connect()的调用返回超时,调用gen_tcp:连接也将返回超时(即{错误,ETIMEDOUT} ),即使较大的超时指定。 | |||
指定要连接的选项的默认值会受到内核配置参数 inet_default_connect_options的影响。有关详细信息,请参见 inet(3)。 | |||
参数 Options 的一些常用选项: | |||
{active, true}:套接字设置为主动模式。所有套接字接收到的消息都作为 Erlang 消息转发到拥有这个套接字进程上。当开启一个套接字时,默认是主动模式。 | |||
{active, false}:设置套接字为被动模式。套接字收到的消息被缓存起来,进程必须通过调用函数 gen_tcp:recv/2 或 gen_tcp:recv/3 来读取这些消息。 | |||
{active, once}:将设置套接字为主动模式,但是一旦收到第一条消息,就将其设置为被动模式,并使用 gen_tcp:recv/2 或 gen_tcp:recv/3 函数来读取后续消息。 | |||
{keepalive, true}:当没有转移数据时,确保所连接的套接字发送保持活跃(keepalive)的消息。因为关闭套接字消息可能会丢失,如果没有接收到保持活跃消息的响应,那么该选项可确保这个套接字能被关闭。默认情况下,该标签是关闭的。 | |||
{nodelay, true}:数据包直接发送到套接字,不过它多么小。在默认情况下,此选项处于关闭状态,并且与之相反,数据被聚集而以更大的数据块进行发送。 | |||
{packet_size, Size}:设置数据包允许的最大长度。如果数据包比 Size 还大,那么将认为这个数据包无效。 | |||
{packet, 0}:表示 Erlang 系统会把 TCP 数据原封不动地直接传送给应用程序 | |||
{reuseaddr, true}:允许本地重复使用端口号 | |||
{delay_send, true}:数据不是立即发送,而是存到发送队列里,等 socket 可写的时候再发送 | |||
{backlog, 1024}:缓冲区的长度 | |||
{exit_on_close, false}:设置为 flase,那么 socket 被关闭之后还能将缓冲区中的数据发送出去 | |||
{send_timeout, 15000}:设置一个时间去等待操作系统发送数据,如果底层在这个时间段后还没发出数据,那么就会返回 {error,timeout} | |||
{Rand, _RandSeed} = random:uniform_s(9999, erlang:now()), | |||
Port = 40000 + Rand, | |||
gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]). | |||
#### accept(ListenSocket) -> {ok, Socket} | {error, Reason} accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason} | |||
Types | |||
ListenSocket = socket() Returned by listen/2. | |||
Timeout = timeout() Socket = socket() Reason = closed | timeout | system_limit | inet:posix() | |||
在侦听套接字上接受传入的连接请求。 套接字必须是从listen / 2返回的套接字 。 超时以毫秒为单位指定超时值。默认为infinity。 | |||
返回值: | |||
{ok, Socket} if a connection is established | |||
{error, closed} if ListenSocket is closed | |||
{error, timeout} if no connection is established within the specified time | |||
{error, system_limit} if all available ports in the Erlang emulator are in use | |||
A POSIX error value if something else goes wrong, see inet(3) for possible error values | |||
用法: | |||
该函数会引起进程阻塞,直到有一个连接请求发送到监听的套接字。 | |||
如果连接已建立,则返回 {ok,Socket}; | |||
或如果 ListenSocket 已经关闭,则返回{error,closed}; | |||
或如果在指定的时间内连接没有建立,则返回{error,timeout}; | |||
或如果 Erlang 虚拟机里可用的端口都被使用了,则返回 {error, system_limit}; | |||
如果某些东西出错,也可能返回一个 POSIX 错误。一些有可能的错误请查看 inet 模块的相关说明。 | |||
使用 gen_tcp:send/2 向该函数返回的套接字 Socket 发送数据包。往端口发送的数据包会以下面格式的消息发送: | |||
{tcp, Socket, Data} | |||
如果在建立套接字 Socket 的时候选项列表中指定了 {active,false},这样就只能使用 gen_tcp:recv/2 或 gen_tcp:recv/3 来接收数据包了。 | |||
{Rand, _RandSeed} = random:uniform_s(9999, erlang:now()), | |||
Port = 40000 + Rand, | |||
case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of | |||
{ok, ListenSocket} -> | |||
case gen_tcp:accept(ListenSocket) of | |||
{ok, Socket} -> | |||
Socket; | |||
{error, SocketAcceptFail} -> | |||
SocketAcceptFail | |||
end; | |||
_ -> | |||
socket_listen_fail | |||
end. | |||
#### connect(Address, Port, Options) -> {ok, Socket} | {error, Reason} | |||
#### connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason} | |||
Types | |||
Address = inet:socket_address() | | |||
inet:hostname() Port = inet:port_number() | |||
Options = [connect_option()] | |||
Timeout = timeout() | |||
Socket = socket() | |||
Reason = timeout | inet:posix() | |||
连接一个 TCP 端口 | |||
用法: | |||
connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason} | |||
用给出的端口 Port 和 IP 地址 Address 连接到一个服务器上的 TCP 端口上。参数 Address 即可以是一个主机名,也可以是一个 IP 地址。 | |||
提供以下选项: | |||
{ip, Address} | |||
如果主机有许多网络接口,则此选项指定要使用的接口。 | |||
{ifaddr, Address} | |||
与{ip,Address}相同。如果主机有许多网络接口,则此选项指定要使用的接口。 | |||
{fd, integer() >= 0} | |||
如果以某种方式未使用gen_tcp连接了套接字 ,请使用此选项传递文件描述符。如果将{ip,Address}和/或 {port,port_number()}与该选项结合使用,则 在连接前将fd绑定到指定的接口和端口。如果未指定这些选项,则假定fd已被适当绑定。 | |||
inet | |||
为IPv4设置套接字。 | |||
inet6 | |||
设置用于IPv6的套接字。 | |||
local | |||
设置Unix域套接字。见 inet:local_address() | |||
{port,Port} | |||
指定要使用的本地端口号。 | |||
{tcp_module, module()} | |||
覆盖使用哪个回调模块。默认为 inet_tcp IPv4和inet6_tcp使用IPv6。 | |||
Opt | |||
参见 inet:setopts / 2。 | |||
可以使用send / 2将数据包发送到返回的套接字Socket。 从对等方发送的数据包将作为消息传递: | |||
{tcp, Socket, Data} | |||
如果套接字处于{active,N}模式(有关详细信息,请参见inet:setopts / 2),并且其消息计数器降至0,则将传递以下消息以指示套接字已转换为被动({active,false}) 模式: | |||
{tcp_passive, Socket} | |||
如果套接字已关闭,则会发出以下消息: | |||
{tcp_closed, Socket} | |||
如果套接字上发生错误,则会传递以下消息(除非在套接字的选项列表中指定了{active,false},在这种情况下,可通过调用recv / 2来检索数据包): | |||
{tcp_error, Socket, Reason} | |||
可选的Timeout参数指定超时(以毫秒为单位)。默认为infinity。 | |||
注意::: | |||
请记住,如果底层OS connect()的调用返回超时,调用gen_tcp:连接也将返回超时(即{错误,ETIMEDOUT} ),即使较大的超时指定。 | |||
指定要连接的选项的默认值会受到内核配置参数 inet_default_connect_options的影响。有关详细信息,请参见 inet(3)。 | |||
#### gen_tcp:close/1 | |||
Types | |||
Socket = socket() | |||
关闭一个 TCP 套接字 | |||
请注意,在大多数TCP实现中,执行关闭操作并不能保证在远程端检测到关闭之前,已发送的任何数据都会传递给接收方。如果要保证将数据传递给收件人,可以通过两种常用方法来实现。 | |||
使用gen_tcp:shutdown(Sock,write)发出信号,表明不再发送任何数据,并等待套接字的读取端关闭。 | |||
使用套接字选项{packet,N}(或类似的选项)可以使接收器在知道已接收到所有数据时关闭连接。 | |||
#### recv(Socket, Length) -> {ok, Packet} | {error, Reason} | |||
#### recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason} | |||
Types | |||
Socket = socket() | |||
Length = integer() >= 0 | |||
Timeout = timeout() | |||
Packet = string() | binary() | HttpPacket | |||
Reason = closed | timeout | inet:posix() | |||
HttpPacket = term() 看到的描述 HttpPacket中 的erlang:decode_packet / 3 在ERTS。 | |||
在被动模式下从套接字接收数据包。 | |||
返回值{error,closed}指示关闭的套接字。 | |||
Argument Length is only meaningful when the socket is in raw mode and denotes the number of bytes to read. | |||
参数 Length 仅在套接字处于 raw mode 时才有意义,它表示要读取的字节数。 | |||
Length为0,则返回所有可用字节。 | |||
如果Length > 0,则返回确切的 Length字节,否则返回错误; | |||
从另一侧关闭套接字时,可能会丢弃少于长度字节的数据 | |||
可选的Timeout参数指定超时(以毫秒为单位)。默认为infinity。 | |||
用法: | |||
recv(Socket, Length, Timeout) -> {ok, Packet} | {error, Reason} | |||
这个函数是从一个被动模式的套接字接受一个数据包。如果返回一个 {error, closed} 的返回值,那表明 Socket 已经关闭。 | |||
当 Socket 是 raw 模式下,参数 Length 才有意义的,并且 Length 表示接收字节的大小。如果 Length = 0,所有有效的字节数据都会被接收。如果 Length > 0,则只会接收 Length 长度的字节,或发生错误;当另一端 Socket 关闭时,接收的数据长度可能会小于 Length。 | |||
选项 Timeout 是一个以毫秒为单位的超时值,默认值是 infinity。 | |||
{Rand, _RandSeed} = random:uniform_s(9999, erlang:now()), | |||
Port = 40000 + Rand, | |||
case gen_tcp:listen(Port, [binary, {packet, 0}, {active, false}]) of | |||
{ok, ListenSocket} -> | |||
case gen_tcp:accept(ListenSocket) of | |||
{ok, Socket} -> | |||
gen_tcp:recv(Socket, 0, 5000); | |||
{error, SocketAcceptFail} -> | |||
SocketAcceptFail | |||
end; | |||
_ -> | |||
socket_listen_fail | |||
end. | |||
#### send(Socket, Packet) -> ok | {error, Reason} | |||
Types | |||
Socket = socket() | |||
Packet = iodata() | |||
Reason = closed | inet:posix() | |||
在一个套接字 Socket 发送一个数据包 | |||
用法: | |||
send(Socket, Packet) -> ok | {error, Reason} | |||
在一个套接字 Socket 发送一个数据包。 | |||
#### shutdown(Socket, How) -> ok | {error, Reason} | |||
Types | |||
Socket = socket() | |||
How = read | write | read_write | |||
Reason = inet:posix() | |||
在一个或两个方向上关闭socket | |||
以某种方式半关闭一个套接字。 | |||
如果参数 How 为 write 的形式,则套接字 socket 会关闭数据写入,读取仍可以正常执行。 | |||
如果How == read或Socket端口没有缓冲传出数据,则套接字将立即关闭,并且Reason中将返回遇到的任何错误。 | |||
要实现套接字半打开, 那么套接字要设置 {exit_on_close, false} 这个参数。 | |||
如果套接字端口中缓冲了数据,则将尝试关闭套接字的操作推迟到该数据写入内核套接字发送缓冲区中为止。 | |||
如果遇到任何错误,则关闭套接字,并在下一个recv / 2或 send / 2上返回 {error,closed}。 | |||
如果对等方在写端执行了关闭操作,则选项{exit_on_close,false}很有用。 | |||
#### gen_tcp:controlling_process/2 | |||
改变一个套接字的控制进程 | |||
将新的控制过程Pid分配给 Socket。控制过程是从套接字接收消息的过程。 | |||
如果由当前控制进程以外的任何其他进程调用, 则返回{error,not_owner}。 | |||
如果由Pid标识的进程不是现有的本地pid, 则返回{error,badarg}。 | |||
在某些情况下,在执行此函数期间关闭Socket时,也可能返回{error,badarg}。 | |||
如果套接字设置为活动模式,则此功能会将呼叫者邮箱中的所有消息传送到新的控制进程。 | |||
如果在传输过程中有任何其他进程正在与套接字交互,则传输可能无法正常进行,并且消息可能会保留在呼叫者的邮箱中。 | |||
例如,在传输完成之前更改套接字活动模式可能会导致此情况 | |||
#### 套接字选项 | |||
{active, true | false | once | -32768..32767} | | |||
如果值为true,这是默认值,则将从套接字接收的所有内容作为消息发送到接收进程。 | |||
如果值为false(被动模式),则该进程必须通过调用gen_tcp:recv / 2,3, gen_udp:recv / 2,3或gen_sctp:recv / 1,2来显式接收传入的数据 (取决于套接字的类型) )。 | |||
如果该值为一次({active,once}), 则套接字中的一条数据消息将发送到该进程。要接收更多消息, 必须使用选项{active,一次}再次调用 setopts / 2。 | |||
如果该值是-32768到32767(含)之间的整数N,则将该值添加到发送到控制进程的套接字的数据消息计数中。 | |||
套接字的默认消息计数为0。如果指定了负值,并且其大小等于或大于套接字的当前消息计数,则套接字的消息计数将设置为0。 | |||
一旦套接字的消息计数达到0,则可能是由于 向进程发送接收到的数据消息或通过显式设置该消息, | |||
然后通过特定于套接字类型的特殊消息通知该进程套接字已进入被动模式。 一旦套接字进入被动模式,为了接收更多消息, | |||
必须再次调用setopts / 2才能将套接字设置回主动模式。 | |||
如果该值是-32768到32767(含)之间的整数N,则将该值添加到发送到控制进程的套接字的数据消息计数中。套接字的默认消息计数为0。 | |||
如果指定了负值,并且其大小等于或大于套接字的当前消息计数,则套接字的消息计数将设置为0。一旦套接字的消息计数达到0, | |||
要么是由于向进程发送接收到的数据消息,要么是因为已显式设置它,然后通过特定于套接字类型的特殊消息通知该进程该套接字已进入被动模式。 | |||
一旦套接字进入被动模式,为了接收更多消息,必须再次调用setopts / 2才能将套接字设置回主动模式。 | |||
使用{active,一次}或{active,N}时,套接字在接收到数据时会自动更改行为。与面向连接的套接字(即gen_tcp)结合使用时, | |||
可能会造成混淆,因为具有{active,false}行为的套接字报告的关闭方式与具有{active,true} 行为的套接字关闭的方式不同。为了简化编程, | |||
当套接字在{active,false}模式下被关闭且对等方关闭时, 在设置为{active,一旦}时仍会生成消息 {tcp_closed,Socket }, | |||
{active,true}或{active,N}模式。因此可以肯定地假设,当套接字在{active,true}和 {active,false}模式之间来回切换时, | |||
消息 {tcp_closed,Socket}可能最终会出现套接字端口终止(取决于选项exit_on_close)。 | |||
但是, 当检测到对等关闭时,完全取决于基础的TCP / IP堆栈和协议。 | |||
注意{active,true}模式不提供流量控制;快速的发送者可以轻松地使接收者的传入消息溢出。对于 {active,N}模式,消息数大于零时也是如此。 | |||
仅当高级协议提供自己的流控制(例如,确认收到的消息)或交换的数据量很少时,才使用活动模式。{active,false} 模式, | |||
使用{active,一旦}模式或{active,N} 模式(具有适用于应用程序的N值)提供流量控制。另一端发送的速度不能超过接收器可以读取的速度。 | |||
{broadcast, Boolean} (UDP sockets) | |||
启用/禁用发送广播的权限。 | |||
{buffer, integer() >= 0} | | |||
驱动程序使用的用户级缓冲区的大小。不要与sndbuf 和recbuf选项混淆,它们与内核套接字缓冲区相对应。对于TCP,建议使用val(buffer)> = val(recbuf), | |||
以避免由于不必要的复制而导致的性能问题。对于UDP,适用相同的建议,但最大值不应大于网络路径的MTU。 | |||
设置recbuf时,val(buffer)会自动设置为上述最大值。但是,为Recbuf设置的大小 通常变大,建议您使用 getopts / 2 来分析操作系统的行为。 | |||
请注意,这也是从单个recv调用可以接收的最大数据量。如果您使用的MTU高于正常值,请考虑将缓冲区设置为更高。 | |||
{delay_send, boolean()} | | |||
通常,当Erlang进程发送到套接字时,驱动程序会尝试立即发送数据。如果失败,驱动程序将使用任何可用方法将要发送的消息排队, | |||
只要操作系统表示可以处理该消息。设置{delay_send,true} 会使所有消息排队。这样,发送到网络的消息就更大, | |||
但更少。该选项将影响发送请求与Erlang进程的调度,而不是更改套接字的任何实际属性。该选项是特定于实现的。默认为false。 | |||
{deliver, port | term} | | |||
当{active,true}时,数据在以下端口上传递 {S, {data, [H1,..Hsz | Data]}} or term : {tcp, S, [H1..Hsz | Data]}. | |||
{dontroute, boolean()} | | |||
启用/禁用传出消息的路由旁路 | |||
{exit_on_close, boolean()} | | |||
默认情况下,此选项设置为true。 | |||
将其设置为false的唯一原因是,如果要在检测到关闭后继续向套接字发送数据,例如,如果对等方使用 gen_tcp:shutdown / 2 关闭写端。 | |||
{header, integer() >= 0} | | |||
仅当创建套接字时指定了选项binary 时,此选项才有意义。如果指定了选项 header, | |||
则从套接字接收的数据的第一个 Size Number字节是列表的元素,其余数据是指定为同一列表尾部的二进制文件。例如,如果Size == 2,则接收到的数据与[Byte1,Byte2 | Binary]匹配 | |||
{high_msgq_watermark, integer() >= 1} | | |||
当消息队列上的数据量达到此限制时,套接字消息队列将设置为繁忙状态。请注意,此限制仅涉及尚未达到ERTS内部套接字实现的数据。默认为8 kB。 | |||
如果套接字消息队列繁忙或套接字本身繁忙,则挂起套接字的数据发送器。 | |||
有关更多信息,请参见选项low_msgq_watermark, high_watermark和low_watermark。 | |||
Notice that distribution sockets disable the use of high_msgq_watermark and low_msgq_watermark. Instead use the distribution buffer busy limit, which is a similar feature. | |||
{high_watermark, integer() >= 0} | | |||
当ERTS套接字实现在内部排队的数据量达到此限制时,将套接字设置为繁忙状态。默认为8 kB。 | |||
如果套接字消息队列繁忙或套接字本身繁忙,则挂起套接字的数据发送器。 | |||
有关更多信息,请参见选项low_watermark, high_msgq_watermark和low_msqg_watermark。 | |||
{ipv6_v6only, Boolean} | |||
限制套接字仅使用IPv6,禁止任何IPv4连接。这仅适用于IPv6套接字(选项inet6)。 | |||
在大多数平台上,必须先在套接字上设置此选项,然后才能将其与地址关联。因此,仅在创建套接字时指定它,而在调用包含此描述的函数(setopts / 2)时不使用它是合理的。 | |||
将此选项设置为true的套接字的行为 是唯一可移植的行为。现在,FreeBSD不建议使用IPv6的初衷是将IPv6用于所有流量(您可以使用 {ipv6_v6only,false}来覆盖建议的系统默认值),但OpenBSD(受支持的GENERIC内核)禁止使用,并且在Windows(具有单独的IPv4和IPv6协议栈)。大多数Linux发行版的系统默认值仍为false。逐渐改变了操作系统之间从IPv4流量中分离IPv6流量的策略,因为逐渐证明,要确保正确,安全地实现双堆栈实施是困难而复杂的。 | |||
在某些平台上,此选项唯一允许的值为true,例如OpenBSD和Windows。在这种情况下,尝试在创建套接字时将此选项设置为false会失败。 | |||
在不存在的平台上设置此选项将被忽略。使用getopts / 2获取此选项 不会返回任何值,即返回的列表不包含 {ipv6_v6only,_}元组。在Windows上,该选项不存在,但会将其模拟为值为true的只读选项。 | |||
因此, 在创建套接字时将此选项设置为true永远不会失败,除非可能是在您已将内核自定义为仅允许false的平台上进行,但在OpenBSD上这是可行的(但尴尬)。 | |||
如果使用getopts / 2读回选项值 而没有获取任何值,则该选项在主机操作系统中不存在。IPv6和IPv4套接字在同一端口上侦听的行为以及获取IPv4流量的IPv6套接字的行为不再可预测。 | |||
{keepalive, boolean()} | | |||
没有其他数据交换时,启用/禁用连接的套接字上的定期传输。如果另一端没有响应,则认为连接已断开,并且将错误消息发送到控制过程。默认为禁用。 | |||
{linger, {boolean(), integer() >= 0}} | | |||
确定在close / 1套接字调用中刷新未发送数据的超时(以秒为单位)。 | |||
第一个组件是如果启用了延迟,第二个组件是刷新超时(以秒为单位)。有3种选择: | |||
{false,_} | |||
close / 1或shutdown / 2会立即返回,而不是等待刷新数据,而在后台进行关闭。 | |||
{true,0} | |||
关闭连接时中止连接。丢弃仍保留在发送缓冲区中的所有数据,并将RST发送给对等方。 | |||
这避免了TCP的TIME_WAIT状态,但是使创建该连接的另一个“化身”成为可能。 | |||
当时间> 0时,{true,时间} | |||
在成功发送了套接字的所有排队消息或达到了超时(时间)之前,close / 1或shutdown / 2不会返回。 | |||
{low_msgq_watermark, integer() >= 1} | | |||
如果套接字消息队列处于繁忙状态,则当消息队列中排队的数据量低于此限制时,套接字消息队列将设置为不繁忙状态。请注意,此限制仅涉及尚未达到ERTS内部套接字实现的数据。默认为4 kB。 | |||
当套接字消息队列和套接字不繁忙时,将恢复由于繁忙的消息队列或繁忙的套接字而挂起的发件人。 | |||
有关更多信息,请参见选项high_msgq_watermark, high_watermark和low_watermark。 | |||
请注意,分发套接字禁止使用 high_msgq_watermark和low_msgq_watermark。而是使用 分配缓冲区繁忙限制,这是一个类似功能。 | |||
{low_watermark, integer() >= 0} | | |||
如果套接字处于繁忙状态,则当ERTS套接字实现在内部排队的数据量低于此限制时,会将套接字设置为不繁忙状态。默认为4 kB。 | |||
当套接字消息队列和套接字不繁忙时,将恢复由于繁忙的消息队列或繁忙的套接字而挂起的发件人。 | |||
有关更多信息,请参见选项high_watermark, high_msgq_watermark和low_msgq_watermark | |||
{mode, list | binary} | | |||
接收到的数据包按照list或者binary的定义进行传递。 | |||
list | | |||
接收到的数据包以列表形式发送。 | |||
binary | | |||
接收到的数据包以二进制形式传送 | |||
{bind_to_device,Ifname :: binary()} | |||
将套接字绑定到特定的网络接口。必须在创建套接字的函数调用中使用此选项,即 gen_tcp:connect / 3,4, gen_tcp:listen / 2, gen_udp:open / 1,2或 gen_sctp:open / 0,1,2。 | |||
与getifaddrs / 0不同,Ifname编码为二进制。如果系统在网络设备名称中使用非7位ASCII字符(这种情况不太可能发生),则在对该参数进行编码时必须格外小心。 | |||
此选项使用特定于Linux的套接字选项 SO_BINDTODEVICE,例如在Linux内核2.0.30或更高版本中,因此仅在针对此类操作系统编译运行时系统时才存在。 | |||
在Linux 3.8之前,可以设置此套接字选项,但无法使用getopts / 2进行检索。从Linux 3.8开始,它是可读的。 | |||
虚拟机还需要提升的特权,这些特权可以以超级用户身份运行,或者(对于Linux)具有CAP_NET_RAW能力 。 | |||
此选项的主要用例是将套接字绑定到 Linux VRF实例。 | |||
{nodelay, boolean()} | | |||
{nodelay,布尔值}(TCP / IP套接字) | |||
如果Boolean == true, 则为套接字打开选项TCP_NODELAY,这意味着也会立即发送少量数据。 | |||
{nopush,布尔型}(TCP / IP套接字) | |||
这相当于TCP_NOPUSH在BSD和TCP_CORK在Linux上。 | |||
如果Boolean == true,则为套接字打开相应的选项,这意味着将累积少量数据,直到可用完整的MSS数据为止或关闭此选项。 | |||
请注意,虽然OSX上提供了TCP_NOPUSH套接字选项,但其语义却大不相同(例如,取消设置它不会导致立即发送累积的数据)。因此,在OSX上有意忽略了nopush选项 | |||
{packet, 0 | 1 | 2 | 4 | raw | sunrm | asn1 | cdr | fcgi | line | tpkt | http | httph | http_bin | httph_bin} | | |||
raw | 0 没有包装。 | |||
1 | 2 | 4 数据包包含一个标头,该标头指定了数据包中的字节数,然后是该字节数。标头长度可以是一个, | |||
两个或四个字节,并包含一个按big-endian字节顺序排列的无符号整数。每个发送操作都会生成标头,并且在每个接收操作上都会剥离标头。4字节的标头限制为2Gb。 | |||
asn1 | cdr | sunrm | fcgi | tpkt | line | |||
这些数据包类型仅对接收有效。发送数据包时,应用程序有责任提供正确的标头。但是,在接收时,对于接收到的每个完整数据包,将一条消息发送到控制过程,并且类似地,对gen_tcp:recv / 2,3的每次调用都 返回一个完整数据包。标头未剥离。 | |||
数据包类型的含义如下: | |||
asn1 -ASN.1 BER | |||
sunrm -Sun的RPC编码 | |||
CDR -CORBA(GIOP 1.1) | |||
fcgi-快速CGI | |||
tpkt -TPKT格式[RFC1006] | |||
line-行模式,数据包以换行符结尾的行,比接收缓冲区长的行被截断 | |||
http | http_bin | |||
超文本传输协议。按照ERTS的erlang:decode_packet / 3中 描述的 HttpPacket格式返回数据包。被动模式下的套接字从gen_tcp:recv返回{ok,HttpPacket}, 而主动套接字发送诸如 {http,Socket,HttpPacket}之类的消息。 | |||
httph | httph_bin | |||
通常不需要这两种类型,因为在读取第一行之后,套接字会在内部自动从http / http_bin切换到 httph / httph_bin。但是,有时可能有用,例如从分块编码中解析预告片 | |||
{packet_size, integer() >= 0} | | |||
设置数据包主体的最大允许长度。如果数据包头指示数据包的长度大于最大允许长度,则该数据包被视为无效。如果数据包头对于套接字接收缓冲区太大,则会发生相同的情况。 | |||
对于面向行的协议(line,http *),选项packet_size还可以保证接受指定长度的行,并且由于内部缓冲区的限制,该行不会被视为无效。 | |||
{line_delimiter, Char}(TCP/IP sockets) | |||
设置面向行的协议(line)的行定界字符。默认为$ \ n。 | |||
{priority, integer() >= 0} | | |||
在实现此功能的平台上设置SO_PRIORITY套接字级别选项。行为和允许范围在不同系统之间有所不同。该选项在未实现的平台上被忽略。请谨慎使用。 | |||
{raw,Protocol :: integer() >= 0,OptionNum :: integer() >= 0, ValueBin :: binary()} | | |||
{read_packets,Integer}(UDP套接字) | |||
设置在数据可用时无需套接字就可以读取的最大UDP数据包数。当读取了这么多的数据包并将其传送到目标进程后,新的数据包才被读取,直到有可用数据的新通知到达为止。默认为5。如果此参数设置得太高,由于UDP数据包泛洪,系统可能会变得无响应。 | |||
{recbuf, integer() >= 0} | | |||
用于套接字的接收缓冲区的最小大小。建议您使用 getopts / 2 来检索操作系统设置的大小。 | |||
{reuseaddr, boolean()} | | |||
允许或禁止端口号的本地重用。默认情况下,不允许重用。 | |||
{send_timeout, integer() >= 0 | infinity} | | |||
仅允许用于面向连接的套接字。 | |||
指定最长时间等待基础TCP堆栈接受发送操作。当超过限制时,发送操作将返回 {error,timeout}。未知发送了多少数据包;因此,只要发生超时,套接字就将关闭(请参见 下面的send_timeout_close)。默认为infinity。 | |||
{send_timeout_close, boolean()} | | |||
仅允许用于面向连接的套接字。 | |||
与send_timeout一起使用,以指定当send操作返回{error,timeout}时是否自动关闭套接字。推荐的设置为 true,它将自动关闭套接字。由于向后兼容,默认为false。 | |||
{show_econnreset, boolean()} | | |||
当此选项设置为false时(默认情况下),将从TCP对等方接收到的RST视为正常关闭(就像已发送FIN一样)。gen_tcp:recv / 2的调用者 获得{错误,关闭}。在活动模式下,控制进程收到 {tcp_closed,Socket}消息,指示对等方已关闭连接。 | |||
将此选项设置为true可让您区分正常关闭的连接和TCP对等方中止(有意或无意)的连接。调用 gen_tcp:recv / 2 返回{error,econnreset}。在活动模式下,控制过程会在通常的{tcp_closed,Socket}之前接收到 {tcp_error,Socket,econnreset}消息,就像其他套接字错误一样。调用 gen_tcp:send / 2 也会返回{error,econnreset} 当检测到TCP对等体已发送RST时。 | |||
从gen_tcp:accept / 1返回的已连接套接字 从侦听套接字 继承了show_econnreset设置。 | |||
{sndbuf, integer() >= 0} | | |||
用于套接字的发送缓冲区的最小大小。鼓励您使用 getopts / 2来检索操作系统设置的大小。 | |||
{tos, integer() >= 0} | | |||
在实现此功能的平台上设置IP_TOS IP级别选项。行为和允许范围在不同系统之间有所不同。该选项在未实现的平台上被忽略。请谨慎使用。 | |||
{tclass, integer() >= 0} | | |||
在实现此功能的平台上 设置IPV6_TCLASS IP级别选项。行为和允许范围在不同系统之间有所不同。该选项在未实现的平台上被忽略。请谨慎使用。 | |||
{ttl, integer() >= 0} | | |||
{recvtos, boolean()} | | |||
{recvtclass, boolean()} | | |||
{recvttl, boolean()} | | |||
option_name() = | |||
active | buffer | delay_send | deliver | dontroute | | |||
exit_on_close | header | high_msgq_watermark | | |||
high_watermark | keepalive | linger | low_msgq_watermark | | |||
low_watermark | mode | nodelay | packet | packet_size | | |||
pktoptions | priority | | |||
{raw,Protocol :: integer() >= 0, OptionNum :: integer() >= 0, ValueSpec ::(ValueSize :: integer() >= 0) | (ValueBin :: binary())} | | |||
recbuf | reuseaddr | send_timeout | send_timeout_close | | |||
show_econnreset | sndbuf | tos | tclass | ttl | recvtos | | |||
recvtclass | recvttl | pktoptions | ipv6_v6only | |||
connect_option() = | |||
{ip, inet:socket_address()} | | |||
{fd, Fd :: integer() >= 0} | | |||
{ifaddr, inet:socket_address()} | | |||
inet:address_family() | | |||
{port, inet:port_number()} | | |||
{tcp_module, module()} | | |||
{netns, file:filename_all()} | | |||
{bind_to_device, binary()} | | |||
option() | |||
listen_option() = | |||
{ip, inet:socket_address()} | | |||
{fd, Fd :: integer() >= 0} | | |||
{ifaddr, inet:socket_address()} | | |||
inet:address_family() | | |||
{port, inet:port_number()} | | |||
{backlog, B :: integer() >= 0} | | |||
{tcp_module, module()} | | |||
{netns, file:filename_all()} | | |||
{bind_to_device, binary()} | | |||
option() | |||
socket() | |||
As returned by accept/1,2 and connect/3,4. |
@ -1,220 +0,0 @@ | |||
#### erlang 各种 优化设置 | |||
一、 erl启动时参数: | |||
+K true 开启epoll调度,在linux中开启epoll,会大大增加调度的效率 | |||
+A 100 异步线程池,为某些port调用服 | |||
+P 1024000 最大进程数 | |||
+Q 65535 最大port数 | |||
+sbt db 绑定调度器,绑定后调度器的任务队列不会在各个CPU线程之间跃迁,结合sub使用,可以让CPU负载均衡的同时也避免了大量的跃迁发生。 | |||
将scheduler绑定到具体的cpu核心上,再配合erlang进程和port绑定,可以显著提升性能,但是如果绑定错误,反而会有反效果 | |||
( 进程调度器绑定:erlang:process_flag(scheduler, 1),当进程使用了port时,还需要port绑定支持,防止进程在不同调度器间迁移引起性能损失,如cache、跨numa node拷贝等,当进程使用了port时,主要是套接字,若进程与port不在一个scheduler上,可能会引发严重的epoll fd锁竞争及跨numa node拷贝,导致性能严重下降) | |||
注意:一个linux系统中,最好只有一个evm开启此选项,若同时有多个erlang虚拟机在系统中运行,还是关闭为好 | |||
+sub true 开启CPU负载均衡,false的时候是采用的CPU密集调度策略,优先在某个CPU线程上运行任务,直到该CPU负载较高为止。 | |||
+swct eager 此选项设置为eager后,CPU将更频繁的被唤醒,可以增加CPU利用率 | |||
+spp true 开启并行port并行调度队列,当开启后会大大增加系统吞吐量,如果关闭,则会牺牲吞吐量换取更低的延迟。 | |||
+zdbbl 65536 分布式erlang的端口buffer大小,当buffer满的时候,向分布式的远程端口发送消息会阻塞 | |||
二、erlang内部进程启动参数 | |||
示例:创建一个新进程并进行注册,该进程是全局唯一的自增ID生成进程,因此无法做多进程处理,这个时候单进程的性能就是至关重要的 | |||
首先,出于性能和功能考虑,这个进程不是gen_server;其次进行了部分参数调优能 | |||
register(num_generator, spawn_opt(?MODULE, init, [],[{priority,high},{scheduler,0},{min_heap_size, 65536 * 2},{min_bin_vheap_size,65536 * 2}])). | |||
参数讲解: | |||
1.priority | |||
erlang是公平调度策略,因此默认情况下每个进程得到的运行时间片是相同的:2000reductions,但是对于我们的应用场景来说,这个进程应该是优先级较高的,需要得到更多的调度,因此设置为high,还可以设置为max,但是max是系统进程的预留优先级,用high即可 | |||
2. scheduler | |||
将该进程绑定到指定的scheduler上,防止进程的任务被scheduler分配来分配去,可以减少CPU调用,注意这个和+sbt db是不同的,+sbt db是防治调度器的任务队列在CPU线程间跃迁,scheduler是为了防止进程在时间片切换过程中被分配给其它的调度器 | |||
3.min_heap_size | |||
进程初始堆大小,用内存换CPU的典型做法,增大初始大小,可以显著降低GC次数和内存再分配次数, 减少处理过程中产生大量term,尤其是list时的gc次数 | |||
4.min_bin_vheap_size | |||
进程初始二进制堆大小,当该进程对于binary数据的处理交换很多时,可以获得和增大min_heap_size一样的效果, 减少大量消息到达或处理过程中产生大量binary时的gc次数 | |||
三、port(socket)调优 | |||
示例:服务器监听端口,接受客户端请求。典型应用场景web服务器,需要实现高吞吐,低延迟的目标 | |||
Res = gen_tcp:listen(Port, [binary, | |||
{reuseaddr, true}, | |||
{nodelay, true}, | |||
{delay_send,true}, | |||
{high_watermark,64 * 1024}, | |||
{send_timeout, 30000}, | |||
{send_timeout_close, true}, | |||
{keepalive, true}]) | |||
参数详解: | |||
binary: | |||
接收到客户端的消息后,作为binary来处理,binary在erlang中是很高效的数据结构,超过64字节,就是全局保存的,因此在很多操作下是不需要复制的,仅仅复制binary的指针即可,详细请搜索refc binary,注意:binary大量使用需要有丰富的经验,不然可能会内存泄漏 | |||
reuseaddr: | |||
允许系统复用port,对于高吞吐的系统,这个参数很重要,请搜索:linux port 复用 | |||
nodelay: | |||
开启linux中的TCP_NODELAY参数,请搜索:TCP_NODELAY 40毫秒延迟 | |||
delay_send: | |||
默认的erlang port消息发送,是直接发送,若失败则排队处理,然后由调度器进行队列poll操作,如果设置为true,那么就不尝试直接发送,而且扔进队列,等待poll,开启选项会增加一点点消息延迟,换来吞吐量的大量提升 | |||
high_watermark: | |||
port的发送缓存,缓存满了后,下次发送会直接阻塞,直到缓存低于某个阈值low_watermark。如果是密集网络IO系统,请增大该buffer,避免发送阻塞 | |||
延迟发送:{delay_send, true},聚合若干小消息为一个大消息,性能提升显著 | |||
发送高低水位:{high_watermark, 128 * 1024} | {low_watermark, 64 * 1024},辅助delay_send使用,delay_send的聚合缓冲区大小为high_watermark,数据缓存到high_watermark后,将阻塞port_command,使用send发送数据,直到缓冲区大小降低到low_watermark后,解除阻塞,通常这些值越大越好,但erlang虚拟机允许设置的最大值不超过128K | |||
发送缓冲大小:{sndbuf, 16 * 1024},操作系统对套接字的发送缓冲大小,在延迟发送时有效,越大越好,但有极值 | |||
接收缓冲大小:{recbuf, 16 * 1024},操作系统对套接字的接收缓冲大小 | |||
send_timeout: | |||
在high_watermark中提到了发送阻塞,如果阻塞超过这个时间,那么就会超时,发送直接返回,停止发送 | |||
send_timeout_close: | |||
如果发生了send_timeout同时设置了send_timeout_close选项,那么超时后,会直接关闭socket.如果发送进程不是很重要,例如web用户进程,强烈建议开启这个选项,当发送30秒超时的时候,就说明该用户出现了很大的麻烦,断开连接是最理想的做法,否则可能出现很多奇怪的bug. | |||
keepalive: | |||
遵循HTTP/1.1协议的keepalive规定,这个根据业务需求选择是否开启,如果同一个客户端会连续发起http请求,那么建议设置为true,避免多次TCP握手 | |||
示例:服务器发起大量的http请求,在优化了参数后,同样的吞吐量所耗费的时间是未优化前的1/3 - 1/2(经过严苛的测试得出的数据) | |||
inets:start(), | |||
httpc:set_options([{max_keep_alive_length,500},{max_sessions,100},{nodelay,true},{reuseaddr,true}]), | |||
参数详解: | |||
max_keep_alive_length: | |||
在同一条http连接上允许发送的最大包数,默认为5,超过5个包,就会重连 | |||
max_sessions: | |||
跟目标服务器之间最大的并行http连接数目,大大的增加了数据上行吞吐量 | |||
nodelay_true: | |||
见上文 | |||
reuseaddr: | |||
6. 数据结构: | |||
减少遍历,尽量使用API提供的操作 | |||
由于各种类型的变量实际可以当做c的指针,因此erlang语言级的操作并不会有太大代价 | |||
lists:reverse为c代码实现,性能较高,依赖于该接口实现的lists API性能都不差,避免list遍历,[||]和foreach性能是foldl的2倍,不在非必要的时候遍历list | |||
dict:find为微秒级操作,内部通过动态hash实现,数据结构先有若干槽位,后根据数据规模变大而逐步增加槽位,fold遍历性能低下 | |||
gb_trees:lookup为微秒级操作,内部通过一个大的元组实现,iterator+next遍历性能低下,比list的foldl还要低2个数量级 | |||
9. 文件预读,批量写,缓存: | |||
这些方式都是局部性的体现: | |||
预读:读空间局部性,文件提供了read_ahead选项 | |||
批量写:写空间局部性 | |||
对于文件写或套接字发送,存在若干级别的批量写: | |||
1. erlang进程级:进程内部通过list缓存数据 | |||
2. erlang虚拟机:不管是efile还是inet的driver,都提供了批量写的选项delayed_write|delay_send, | |||
它们对大量的异步写性能提升很有效 | |||
3. 操作系统级:操作系统内部有文件写缓冲及套接字写缓冲 | |||
4. 硬件级:cache等 | |||
缓存:读写时间局部性,读写空间局部性,主要通过操作系统系统,erlang虚拟机没有内部的缓存 | |||
10.套接字标志设置: | |||
延迟发送:{delay_send, true},聚合若干小消息为一个大消息,性能提升显著 | |||
发送高低水位:{high_watermark, 128 * 1024} | {low_watermark, 64 * 1024},辅助delay_send使用,delay_send的聚合缓冲区大小为high_watermark,数据缓存到high_watermark后,将阻塞port_command,使用send发送数据,直到缓冲区大小降低到low_watermark后,解除阻塞,通常这些值越大越好,但erlang虚拟机允许设置的最大值不超过128K | |||
发送缓冲大小:{sndbuf, 16 * 1024},操作系统对套接字的发送缓冲大小,在延迟发送时有效,越大越好,但有极值 | |||
接收缓冲大小:{recbuf, 16 * 1024},操作系统对套接字的接收缓冲大小 | |||
#### Erlang 虚拟机调优 | |||
目录 | |||
SMP | |||
Schedulers | |||
Port Settings | |||
Asynchronous Thread Pool | |||
Kernel Polling | |||
Warning Messages | |||
Process Limit | |||
Distribution Buffer | |||
Erlang Built-in Storage | |||
Crash Dumps | |||
Net Kernel Tick Time | |||
Shutdown Time | |||
Riak 是用Erlang语言写的,运行在Erlang虚拟机之上.所以Erlang虚拟机的调优对Riak的性能优化就显得尤为重要. Erlang虚拟机本身提供了非常多的配置参数对性能调优, Riak支持其中的一部分参数,你可以在每个node的Riak配置文件中进行设置. | |||
下表列出了其中的一部分,左边一列是Erlang中的参数名称, 右边一列是在Riak中的参数名称. | |||
```Erlang parameter Riak parameter | |||
+A erlang.async_threads | |||
+K erlang.K | |||
+P erlang.process_limit | |||
+Q erlang.max_ports | |||
+S erlang.schedulers.total, erlang.schedulers.online | |||
+W erlang.W | |||
+a erlang.async_threads.stack_size | |||
+e erlang.max_ets_tables | |||
+scl erlang.schedulers.compaction_of_load | |||
+sfwi erlang.schedulers.force_wakeup_interval | |||
-smp erlang.smp | |||
+sub erlang.schedulers.utilization_balancing | |||
+zdbbl erlang.distribution_buffer_size | |||
-kernel net_ticktime erlang.distribution.net_ticktime | |||
-env FULLSWEEP_AFTER erlang.fullsweep_after | |||
-env ERL_CRASH_DUMP erlang.crash_dump | |||
-env ERL_MAX_ETS_TABLES erlang.max_ets_tables | |||
-name nodename | |||
``` | |||
Note on upgrading to 2.0 | |||
在Riak2.0版本之前, Erlang虚拟机相关的参数放在配置文件 vm.args 里面. 在2.0及之后的版本中, 所有Erlang虚拟机相关的配置参数放在配置文件 riak.conf 里面. 如果你从Riak2.0之前的版本升级到Riak 2.0, 你仍然可以继续使用旧的配置文件 vm.args. 但是, 如果你同时设置了配置文件 vm.args 和riak.conf, 在 vm.args里面的配置将会覆盖riak.conf里面的配置. | |||
##### SMP | |||
有些操作系统提供Erlang虚拟机对称多处理器能力(SMP)以利用多处理器硬件架构的优势. SMP的支持可以通过设置erlang.smp参数来打开和关闭, 默认是打开的. 下面的例子是关闭SMP的支持. | |||
riak.conf | |||
erlang.smp = disable | |||
由于Riak也可以运行在一些不支持SMP的操作系统上, 所以在使用之前需要确认操作系统是否支持SMP,如果操作系统本身不支持,那么需要在启动Riak集群之前在配置文件riak.conf中关闭SMP的选项. | |||
比较安全的一个选择是把erlang.smp设置成auto, 这个选项会指示Erlang虚拟机启动SMP支持之前检查操作系统是否支持以及是否有一个以上的逻辑处理器,只有这两个条件都满足的时候,Erlang虚拟机才启动SMP支持. | |||
##### Schedulers | |||
Note on missing scheduler flags | |||
We recommend that all users set the +sfwi to 500 (milliseconds) and the +sclflag to false if using the older, vm.args-based configuration system. If you are using the new, riak.conf-based configuration system, the corresponding parameters are erlang.schedulers.force_wakeup_interval anderlang.schedulers.compaction_of_load. | |||
Please note that you will need to uncomment the appropriate lines in your riak.conf for this configuration to take effect. | |||
如果在Erlang虚拟机里已经打开了支持SMP的选项, 比如erlang.smp已经被设置成enabled 或者auto,而且机器本身超过一个逻辑处理器同时也支持SMP, 那么当你启动Riak的时候, 你可以配置逻辑处理器的数量或者调度线程的数量,同时也可以设置online线程的数量. | |||
全部调度线程的数量可以通过参数erlang.schedulers.total来设置, online线程的数量则是通过参数erlang.schedulers.online来配置. 这两个参数可以分别对应到Erlang虚拟机的参数Schedulers 和SchedulersOnline. | |||
两个参数的最大值都是1024, 参数并没有统一的默认值. 但是, Erlang 虚拟机自己会尝试去判定有多少配置的CPU(core)和可用的CPU(core). 如果Erlang虚拟机能够做出这个判定,那么参数schedulers.total会默认设置成配置的CPU(core)数量, | |||
参数schedulers.online会默认设置成可用的CPU(core)数量. 但是, 如果Erlang虚拟机不能做出判定, 两个参数的默认值将会设置成1. | |||
如果两个参数中的任意一个被设置成负数, 那么意味着这个参数值将会被设成默认配置的处理器数量(如果scheduler.total是负数)或者可用的处理器数量(如果schedulers.online是负数) 减去配置的负值. 比如, 如果机器配置有100个cpu(cores)然后参数schedulers.total配置为-50, 计算以后的值就是50. | |||
如果两个参数中的任意一个被设置为0,两个值都会被重新设为默认值. | |||
如果SMP支持被关闭, 比如erlang.smp被设成disabled或者设成auto 但是机器本身不支持SMP或者机器只有一个逻辑处理器,那么两个参数schedulers.total 和 schedulers.online都将会被忽略. | |||
Scheduler Wakeup Interval | |||
调度器唤醒是一个可选处理, 通过这个Erlang 虚拟机调度器被周期性的扫描来判定是否已经陷入睡眠, 比如是否调度器有一个空的运行列表. 这个扫描时间间隔可以通过参数erlang.schedulers.force_wakeup_interval设置, 单位为毫秒.这个参数对应于Erlang虚拟机的+sfwi选项.该参数默认设为0, 不激活调度器唤醒功能. | |||
Erlang在R15Bx版本里有把调度器睡眠过于频繁的倾向,如果你使用的是更新的版本,比如Riak2.0 及以后, 那多数情况下不需要启动唤醒功能. | |||
注: OTP的工程师曾经解释过这个功能,如果需要调度的任务不是很多,没有很多task在运行列表上的话, R15B的Erlang虚拟机会倾向于把这些task尽量集中到尽可能少的调度器上来调度, 睡眠没有调度任务的调度器, 这样可以减少调度器之间的通信花费overhead, 提高CPU的利用率. 但这个也是一个trade off, 具体还是需要用户来根据自己的实际环境来调优. 因为一旦task的数量增加比较多,或者task数量没有增加但是task本身比较耗时,那么很可能就会触发调度器的唤醒, 而唤醒调度器是比较expensive的操作, 如果频繁睡眠唤醒的话,可能会得不偿失. | |||
##### Scheduler Compaction and Balancing | |||
Erlang调度器提供了两种方式来分发负载到不同的调度器上, 集中负载和utilization balancing. | |||
集中负载是默认打开的, 打开的时候Erlang虚拟机会尝试去尽可能多的使调度器繁忙,比如通过把任务集中到有限的几个调度器上(假设这几个有限的调度器充分运行的情况下可以调度完目前的tasks)使这几个调度器一直有工作做(not run out of work). 为了达到这个目的, 当虚拟机分配任务的时候会考虑哪些调度器应该被分配任务. 用户可以设置参数erlang.schedulers.compaction_of_load为false来关闭这个功能. | |||
另外一个选项, utilization balancing, 为了支持负载平衡, 默认是关闭的. 如果打开了这个选项, Erlang虚拟机则努力在不同调度器之间平衡调度器的利用. 如果不考虑每个调度器没有任务可调度的频度的话, 可以打开这个设置, erlang.schedulers.utilization_balancing 设为true(老版本里面通过设置+scl false) | |||
在任何时候, 只可以是使用两个功能中的一个. 如果同时设置这两个选项为false的话, Riak 会默认使用集中负载选项.如果同时设置为true, Riak会使用那个在配置文件riak.conf中最先出现的那个.(如果是旧版本的话,配置文件会是vm.args) | |||
##### Port Settings | |||
Riak 使用epmd, Erlang 端口映射Daemon来进行大多数的节点间的通信. 在这个系统里, 集群里的其他节点使用由nodename参数(或者是name in vm.args)来作为节点ID. 比如, riak@10.9.8.7. 在每个节点上, daemon把这些节点ID解析成一个TCP的端口. 用户可以指定一个端口范围给Riak节点来监听使用,同时也可以知道最大数量的并ports/sockets. | |||
Port Range | |||
默认情况下 , epmd绑定到TCP端口4369上并且侦听通配符接口. epmd 默认使用一个不能预测的端口作为节点间的通信, 通过绑定到端口0上, 意味着会使用第一个可用的端口. 这样就使得防火墙非常难配置. | |||
为了是防火墙配置简化, 用户可以指导Erlang虚拟机使用一个有限范围的端口或者单一端口. 这个最小和最大值可以设置在参数erlang.distribution.port_minimum和erlang.distribution.port_maximum里面. 比如, 下面的值被设为3000和5000. | |||
riak.conf | |||
app.config | |||
erlang.distribution.port_range.minimum = 3000 | |||
erlang.distribution.port_range.maximum = 5000 | |||
用户可以设置Erlang虚拟机使用一个单一端口, 如果只设置了最小值没有设置最大值,则表示使用单一端口. 比如, 下面设置使用单一端口5000. | |||
riak.conf | |||
app.config | |||
erlang.distribution.port_range.minimum = 5000 | |||
如果最小端口没有设置, Erlang虚拟机将会在随机的高编号端口上侦听. | |||
##### Maximum Ports | |||
用户可以通过设置参数erlang.max_ports来指定Erlang虚拟机可以使用的最大并发的 ports/sockets数量, 范围从1024到134217727. 默认值是65536. 在vm.args里面对应的参数是+Q 或者-env ERL_MAX_PORTS. | |||
Asynchronous Thread Pool | |||
如果Erlang虚拟机支持线程可用, 用户可以为Erlang虚拟机设置异步线程池的线程数量, 使用参数erlang.async_threads(+A in vm.args). 线程数量范围从0至1024, 默认值是64,下面的例子是设置成600的情况. | |||
riak.conf | |||
vm.args | |||
erlang.async_threads = 600 | |||
##### Stack Size | |||
除了可以指定异步线程的数量之外, 用户还可以为每个异步线程指定stack size. 参数是erlang.async_threads.stack_size, 对应到Erlang的+a参数. 用户可以在Riak中为这个参数指定size以KB, MB,GB 为单位, 有效的范围值是16至8192个字, 在32位的系统上就是64至32768字节. 该参数没有默认值, 我们建议设置为16K words, 对应为64 KB在32位系统上. 我们建议这么小一个值是考虑到异步线程数量可能会很大. | |||
注:The 64 KB default is enough for drivers delivered with Erlang/OTP but might not be large enough to accommodate drivers that use the driver_async()functionality, documented here. We recommend setting higher values with caution, always keeping the number of available threads in mind. | |||
Kernel Polling | |||
如果系统支持, 用户可以在Erlang中利用内核轮询. 内核轮询可以在使用很多文件描述符的时候提高性能. 在使用中的文件描述符越多, 内核轮询发挥的作用就越大. 该选择在Riak的Erlang虚拟机中是默认打开的, 该参数对应到Erlang虚拟机中的+K参数 | |||
##### Warning Messages | |||
Erlang虚拟机的error_logger 是一个事件管理器, 从Erlang运行时系统注册错误, 告警和信息事件. 默认情况下, error_logger的信息事件被映射为告警,但是用户可以设置映射成错误或者信息. 该设置为参数erlang.W, 可以设置的值为w(warning), errors 或者i(info reports). | |||
##### Process Limit | |||
参数erlang.process_limit可以用来设置系统同时存在的最大进程数量(对应到Erlang的+P参数), 有效范围从1024至134217727. 默认值是256000. | |||
##### Distribution Buffer | |||
用户可以通过参数erlang.distribution_buffer_size设置Erlang虚拟机的distribution buffer busy limit(对应到Erlang的+zdbbl参数). 修改这个参数对那些有许多busy dist port事件的节点可能会有帮助, 默认值是32MB, 最大值是2097151KB. 增大这个参数可以允许进程缓存更多的待发消息, 当缓存满的时候,发送线程被挂起直到缓存减小到设定值. 所以, 更大的缓存有助于降低延迟以及增加吞吐量,代价就是使用了更多的RAM. 用户需要根据机器的RAM资源来考虑设定这个值. | |||
##### Erlang Built-in Storage | |||
Erlang使用一个内置的数据库,ets(Erlang Term Storage)用来快速访问内存(constant access time rather than logarithmic access time). erts 表的最大数量设置在参数erlang.max_erts_tables里面, 默认值是256000,这个值要大于Erlang虚拟机自身的默认值1400(对应到vm.args 的参数e). 更大的erlang.max_erts_tables值可以提供更快的数据访问,代价是消耗更高的内存. | |||
##### Crash Dumps | |||
默认情况下, Riak 的Erlang crash dumps文件是存放在位置./log/erl_crash.dump. 用户可以通过设置参数erlang.crash_dump来更改存放位置. 该参数对应到Erlang虚拟机的ERL_CRASH_DUMP环境变量. | |||
##### Net Kernel Tick Time | |||
网络内核是Erlang的一个系统进程, 提供了不同的网络监视形式. 在一个Riak集群里面, 网络内核的功能之一就是去周期性的检测节点存活. Tick time就是这个检查频度, 可以通过erlang.distribution.net_ticktime设置,单位是秒. 该参数对应到vm.args里面的参数-kernal net_ticktime. | |||
##### Shutdown Time | |||
用户可以设定Erlang虚拟机的关闭时间, 该设置参数为erlang.shutdown_time,默认是10秒, 一旦10秒过了, 所有存在的进程就会被杀掉. 减少关闭时间在某些情景下可能是有帮助的, 比如说在测试的时候需要频繁的启停Riak集群. 在vm.args里参数是shutdown_time, 单位是毫秒. |
@ -1,300 +0,0 @@ | |||
# erlang各种数据类型占用的内存大小 | |||
有效编程的一个好的开始是知道不同数据类型和操作需要多少内存。 | |||
Erlang数据类型和其他项目消耗多少内存与实现方式有关,但是下表显示了OTP 19.0中erts-8.0系统的一些数据。 | |||
度量单位是存储字。 同时存在32位和64位实现。 因此,一个字分别是4字节或8字节。 | |||
erlang:system_info(wordsize). | |||
``` | |||
Data Type Memory Size | |||
Small integer | |||
1 word. | |||
On 32-bit architectures: -134217729 < i < 134217728 | |||
(28 bits). | |||
On 64-bit architectures: -576460752303423489 < i < | |||
576460752303423488 (60 bits). | |||
Large | |||
integer 3..N words. | |||
Atom | |||
1 word. | |||
An atom refers into an atom table, which also consumes | |||
memory. The atom text is stored once for each unique | |||
atom in this table. The atom table is not garbage-collected. | |||
Float | |||
On 32-bit architectures: 4 words. | |||
On 64-bit architectures: 3 words. | |||
Binary | |||
3..6 words + data (can be shared). | |||
List | |||
1 word + 1 word per element + the size of each element. | |||
String (is the same as a list of integers) | |||
1 word + 2 words per character. | |||
Tuple | |||
2 words + the size of each element. | |||
Small Map | |||
5 words + the size of all keys and values. | |||
Large Map (> 32 keys) | |||
N x F words + the size of all keys and values. | |||
N is the number of keys in the Map. | |||
F is a sparsity factor that can vary | |||
between 1.6 and 1.8 due to the probabilistic nature of | |||
the internal HAMT data structure. | |||
Pid | |||
1 word for a process identifier from the current local | |||
node + 5 words for a process identifier from another | |||
node. | |||
A process identifier refers into a process table and a | |||
node table, which also consumes memory. | |||
Port | |||
1 word for a port identifier from the current local node + | |||
5 words for a port identifier from another node. | |||
A port identifier refers into a port table and a node table, | |||
which also consumes memory. | |||
Reference | |||
On 32-bit architectures: 5 words for a reference from | |||
the current local node + 7 words for a reference from | |||
another node. | |||
On 64-bit architectures: 4 words for a reference from | |||
the current local node + 6 words for a reference from | |||
another node. | |||
A reference refers into a node table, which also | |||
consumes memory. | |||
Fun | |||
9..13 words + the size of environment. | |||
A fun refers into a fun table, which also consumes | |||
memory. | |||
Ets table | |||
Initially 768 words + the size of each element (6 words | |||
+ the size of Erlang data). The table grows when | |||
necessary. | |||
Erlang process | |||
338 words when spawned, including a heap of 233 words. | |||
``` | |||
# System Limits | |||
Erlang语言规范对进程数,原子长度等没有任何限制。 但是,出于性能和内存节省的原因,在Erlang语言和执行环境的实际实现中始终会受到限制。 | |||
``` | |||
Processes | |||
The maximum number of simultaneously alive Erlang | |||
processes is by default 262,144. This limit can be | |||
configured at startup. For more information, see the | |||
+P command-line flag in the erl(1) manual page in | |||
ERTS. | |||
Known nodes | |||
A remote node Y must be known to node X if there | |||
exists any pids, ports, references, or funs (Erlang data | |||
types) from Y on X, or if X and Y are connected. The | |||
maximum number of remote nodes simultaneously/ever | |||
known to a node is limited by the maximum number of | |||
atoms available for node names. All data concerning | |||
remote nodes, except for the node name atom, are | |||
garbage-collected. | |||
Connected nodes | |||
The maximum number of simultaneously connected | |||
nodes is limited by either the maximum number of | |||
simultaneously known remote nodes, the maximum | |||
number of (Erlang) ports available, or the maximum | |||
number of sockets available. | |||
Characters in an atom | |||
255. | |||
Atoms | |||
By default, the maximum number of atoms is 1,048,576. | |||
This limit can be raised or lowered using the +t option. | |||
Elements in a tuple | |||
The maximum number of elements in a tuple is | |||
16,777,215 (24-bit unsigned integer). | |||
Size of binary | |||
In the 32-bit implementation of Erlang, 536,870,911 | |||
bytes is the largest binary that can be constructed | |||
or matched using the bit syntax. In the 64- | |||
bit implementation, the maximum size is | |||
2,305,843,009,213,693,951 bytes. If the limit | |||
is exceeded, bit syntax construction fails with a | |||
system_limit exception, while any attempt to | |||
match a binary that is too large fails. This limit is | |||
enforced starting in R11B-4. | |||
In earlier Erlang/OTP releases, operations on too large | |||
binaries in general either fail or give incorrect results. | |||
In future releases, other operations that create binaries | |||
(such as list_to_binary/1) will probably also | |||
enforce the same limit. | |||
Total amount of data allocated by an Erlang node | |||
The Erlang runtime system can use the complete 32-bit | |||
(or 64-bit) address space, but the operating system often | |||
limits a single process to use less than that. | |||
Length of a node name | |||
An Erlang node name has the form host@shortname | |||
or host@longname. The node name is used as an atom | |||
within the system, so the maximum size of 255 holds | |||
also for the node name. | |||
Open ports | |||
The maximum number of simultaneously open Erlang | |||
ports is often by default 16,384. This limit can be | |||
configured at startup. For more information, see the | |||
+Q command-line flag in the erl(1) manual page in | |||
ERTS. | |||
Open files and sockets | |||
同时打开的文件和套接字的最大数量取决于可用的Erlang端口的最大数量,以及特定于操作系统的设置和限制。 | |||
Number of arguments to a function or fun | |||
255 | |||
Unique References on a Runtime System Instance | |||
Each scheduler thread has its own set of references, | |||
and all other threads have a shared set of references. | |||
Each set of references consist of 2## - 1 unique | |||
references. That is, the total amount of unique | |||
references that can be produced on a runtime system | |||
instance is (NoSchedulers + 1) × (2## - | |||
1). | |||
If a scheduler thread create a new reference each nano | |||
second, references will at earliest be reused after more | |||
than 584 years. That is, for the foreseeable future they | |||
are unique enough. | |||
Unique Integers on a Runtime System Instance | |||
There are two types of unique integers both created | |||
using the erlang:unique_integer() BIF: | |||
1. Unique integers created with the monotonic | |||
modifier consist of a set of 2## - 1 unique integers. | |||
2. Unique integers created without the monotonic | |||
modifier consist of a set of 2## - 1 unique integers | |||
per scheduler thread and a set of 2## - 1 unique | |||
integers shared by other threads. That is, the total | |||
amount of unique integers without the monotonic | |||
modifier is (NoSchedulers + 1) × (2## - | |||
1). | |||
If a unique integer is created each nano second, unique | |||
integers will at earliest be reused after more than 584 | |||
years. That is, for the foreseeable future they are unique | |||
enough. | |||
``` | |||
# Erlang 常用数据结构实现 | |||
erlang虚拟机中用Eterm表示所有的类型的数据,具体的实施方案通过占用Eterm的后几位作为类型标签,然后根据标签类型来解释剩余位的用途。这个标签是多层级的,最外层占用两位,有三种类型: | |||
list,剩下62位是指向列表Cons的指针 | |||
boxed对象,即复杂对象,剩余62位指向boxed对象的对象头。包括元组,大整数,外部Pid/Port等 | |||
immediate立即数,即可以在一个字中表示的小型对象,包括小整数,本地Pid/Port,Atom,NIL等 | |||
这三种类型是Erlang类型的大框架,前两者是可以看做是引用类型,立即数相当于是值类型,但无论对于哪种类型,Erlang Eterm本身只占用一个字,理解这一点是很重要的。 | |||
对于二三级标签的细分和编码,一般我们无需知道这些具体的底层细节,以下是几种常用的数据结构实现方式。 | |||
一. 常用类型 | |||
1. atom | |||
atom用立即数表示,在Eterm中保存的是atom在全局atom表中的索引,依赖于高效的哈希和索引表,Erlang的atom比较和匹配像整数一样高效。atom表是不回收的,并且默认最大值为1024*1024,超过这个限制Erlang虚拟机将会崩溃,可通过+t参数调整该上限。 | |||
2.Pid/Port | |||
在R9B之后,随着进程数量增加和其它因素,Pid只在32位中表示本地Pid(A=0),将32位中除了4位Tag之外的28位,都可用于进程Pid表示, | |||
出于Pid表示的历史原因,仍然保留三段式的显示,本地Pid表示变成了<0, Pid低15位, Pid高13位>。对于外部Pid,采用boxed复合对象表示, | |||
在将本地Pid发往其它node时,Erlang会自动将为Pid加上本地节点信息,并打包为一个boxed对象,占用6个字。另外,Erlang需要维护Pid表, | |||
每个条目占8个字节,当进程数量过大时,Pid表将占用大量内存,Erlang默认可以使用18位有效位来表示Pid(262144),可通过+P参数调节, | |||
最大值为27位(2^27-1),此时Pid表占用内存为2G。 | |||
3. ists | |||
列表以标签01标识,剩余62位指向列表的Cons单元,Cons是[Head|Tail]的组合,在内存中体现为两个相邻的Eterm,Head可以是任何类型的Eterm, | |||
。因此形如L2 = [Elem|L1]的操作,实际上构造了一个新的Cons,其中Head是Elem Eterm,Tail是L1 Eterm,然后将L2的Eterm指向了这个新的Cons, | |||
因此L2即代表了这个新的列表。对于[Elem|L2] = L1,实际上是提出了L1 Eterm指向的Cons,将Head部分赋给Elem,Tail部分赋给L2, | |||
注意Tail本身就是个List的Eterm,因此list是单向列表,并且构造和提取操作是很高效的。需要再次注意的是,Erlang所有类型的Eterm本身只占用一个字大小。 | |||
这也是诸如list,tuple能够容纳任意类型的基础。 | |||
Erlang中进程内对对象的重复引用只需占用一份对象内存(只是Eterm本身一个字的拷贝),但是在对象跨进程时,对象会被展开,执行速深度拷贝: | |||
4. tuple | |||
tuple属于boxed对象的一种,每个boxed对象都有一个对象头(header),boxed Eterm即指向这个header,这个header里面包含具体的boxed对象类型, | |||
如tuple的header末6位为000000,前面的位数为tuple的size: | |||
tuple实际上就是一个有头部的数组,其包含的Eterm在内存中紧凑排列,tuple的操作效率和数组是一致的。 | |||
list,tuple中添加元素,实际上都是在拷贝Eterm本身,Erlang虚拟机会追踪这些引用,并负责垃圾回收。 | |||
5. binary | |||
Erlang binary用于处理字节块,Erlang其它的数据结构(list,tuple,record)都是以Eterm为单位的,用于处理字节块会浪费大量内存 | |||
,如”abc”占用了7个字(加上ETerm本身),binary为字节流提供一种操作高效,占用空间少的解决方案。 | |||
之前我们介绍的数据结构都存放在Erlang进程堆上,进程内部可以使用对象引用,在对象跨进程传输时,会执行对象拷贝。 | |||
为了避免大binary跨进程传输时的拷贝开销,Erlang针对binary作出了优化,将binary分为小binary和大binary。 | |||
heap binary | |||
小于64字节(定义于erl_binary.h ERL_ONHEAP_BIN_LIMIT宏)的小binary直接创建在进程堆上,称为heap binary,heap binary是一个boxed对象: | |||
refc binary | |||
大于64字节的binary将创建在Erlang虚拟机全局堆上,称为refc binary(reference-counted binary),可被所有Erlang进程共享, | |||
这样跨进程传输只需传输引用即可,虚拟机会对binary本身进行引用计数追踪,以便GC。refc binary需要两个部分来描述, | |||
位于全局堆的refc binary数据本身和位于进程堆的binary引用(称作proc binary),这两种数据结构定义于global.h中。 | |||
下图描述refc binary和proc binary的关系: | |||
所有的OffHeap(进程堆之外的数据)被组织为一个单向链表,进程控制块(erl_process.h struct process)中的off_heap字段维护链表头和所有OffHeap对象的总大小, | |||
当这个大小超过虚拟机阀值时,将导致一次强制GC。注意,refc binary只是OffHeap对象的一种,以后可扩展其它种类。 | |||
sub binary | |||
sub binary是Erlang为了优化binary分割的(如split_binary/2),由于Erlang变量不可变语义,拷贝分割的binary是效率比较底下的做法,Erlang通过sub binary来复用原有binary。 | |||
bit string | |||
当我们通过如<<2:3,3:6>>的位语法构建binary时,将得到<<65,1:1>>这种非字节对齐的数据,即二进制流, | |||
在Erlang中被称为bitstring,Erlang的bitstring基于ErlSubBin结构实现,此时bitsize为最后一个字节的有效位数, | |||
size为有效字节数(不包括未填满的最后一个字节),对虚拟机底层来说,sub bianry和bit string是同一种数据结构。 | |||
## 复合类型 | |||
1. record | |||
这个类型无需过多介绍,它就是一个tuple,所谓record filed在预编译后实际上都是通过数值下标来索引,因此它访问field是O(1)复杂度的。 | |||
2. map | |||
该结构体之后就是依次存放的Value,因此maps的get操作,需要先遍历keys tuple,找到key所在下标,然后在value中取出该下标偏移对应的值。因此是O(n)复杂度的。详见maps:get源码($BEAM_SRC/erl_map.c erts_maps_get)。 | |||
如此的maps,只能作为record的替用,并不是真正的Key->Value映射,因此不能存放大量数据。而在OTP18中,maps加入了针对于big map的hash机制, | |||
当maps:size < MAP_SMALL_MAP_LIMIT时,使用flatmap结构,也就是上述OTP17中的结构,当maps:size >= MAP_SMALL_MAP_LIMIT时, | |||
将自动使用hashmap结构来高效存取数据。MAP_SMALL_MAP_LIMIT在erl_map.h中默认定义为32。 | |||
仍然要注意Erlang本身的变量不可变原则,每次执行更新maps,都会导致新开辟一个maps,并且拷贝原maps的keys和values,在这一点上,maps:update比maps:put更高效,因为前者keys数量不会变,因此无需开辟新的keys tuple,拷贝keys tuples ETerm即可。实际使用maps时: | |||
更新已有key值时,使用update(:=)而不是put(=>),不仅可以检错,并且效率更高 | |||
当key/value对太多时,对其进行层级划分,保证其拷贝效率 | |||
实际测试中,OTP18中的maps在存取大量数据时,效率还是比较高的,这里有一份maps和dict的简单测试函数,可通过OTP17和OTP18分别运行来查看效率区别。通常情况下,我们应当优先使用maps,比起dict,它在模式匹配,mongodb支持,可读性上都有很大优势。 | |||
3. array | |||
array下标从0开始 | |||
array有两种模式,一种固定大小,另一种按需自动增长大小,但不会自动收缩 | |||
支持稀疏存储,执行array:set(100,value,array:new()),那么[0,99]都会被设置为默认值(undefined),该默认值可修改。 | |||
在实现上,array最外层被包装为一个record: | |||
... 其他等待被添加 | |||
## 顺序 | |||
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string | |||
%% Module Description | |||
%% sets sets, a collection of unique elements. | |||
%% gb_sets sets, but based on a general balanced data structure | |||
%% gb_tree a general balanced tree | |||
%% dict maps, also called associative arrays | |||
%% queue double-ended queues | |||
%% ets hash tables and ordered sets (trees), stored outside the process | |||
%% dets on-disk hash tables | |||
(请注意:不常用的模块ordset和 orddict只是有序列表,因此对于诸如插入之类的常见操作具有O(n)) | |||
# Erlang标准数据结构的选择 | |||
实际上,Erlang程序使用列表(本机或通过dict)来处理涉及多达数百个元素的数据结构, | |||
并使用ETS(Erlang术语存储)或mnesia来处理更大的数据。 | |||
ETS使用散列来允许几乎恒定时间访问几乎任意数量的数据。 | |||
对于由几个(几十个或几百个)项组成的数据集合,列表通常要优于ETS和树。 对于大量小物品,ETS往往效果最好。 | |||
对于较大的项目,数据插入ets和从ets读取都会复制数据, 需要掂量。 | |||
lists ,maps 和record是erlang最为常用的数据结构,lists使用方便简单,maps则查询高效,record则需要预定义, | |||
对比测试数据maps在查询性能上比lists高, 而在遍历上lists则更优。对于频繁插入和查询的数据,maps是最佳的选择, | |||
record在数据量小的情况下 插入 更新 查询效率都很高, 而且使用的是模式匹配也很方便 | |||
lists则适用于广播列表之类需要遍历的数据和数据量少的情况。 | |||
更多数据结构 | |||
utPdDs, utArrayDs, utTupleDs, utListsDs, utMapsDs, utEtsSetDs, utEtsOrdDs, utDictDs, utGb_treesDs, utSetsDs, utGb_setsDs, utOrddictDs, utOrdsetsDs, utAtomicsDs, utPTermDs | |||
测试代码见 testCase/DsTest | |||
数据结构测评结果见 dosc/erlang-DsBenchMark.txt |
@ -1,344 +0,0 @@ | |||
Erlang 进程相关学习 | |||
==================================== | |||
# 目录 | |||
1. [actor模型和进程特性](#actor模型和进程特性) | |||
2. [进程创建](#进程创建) | |||
3. [进程监控与注册](#进程注册与监控) | |||
4. [进程调度](#进程调度) | |||
5. [进程发送消息](#进程发送消息) | |||
6. [进程接收消息](#进程接收消息) | |||
7. [进程GC](#进程GC) | |||
8. [更多](#更多) | |||
## actor模型和进程特性 | |||
### actor模型 | |||
在计算机科学中,它是一个并行计算的数学模型,最初为由大量独立的微处理器组成的高并行计算机所开发,Actor模型的理念非常简单: | |||
天下万物皆为Actor。Actor之间通过发送消息来通信,消息的传送是异步的,通过一个邮件队列(mail queue)来处理消息。每个Actor | |||
是完全独立的,可以同时执行它们的操作。每一个Actor是一个计算实体,映射接收到的消息到以下动作: | |||
1. 发送有限个消息给其它Actor | |||
2. 创建有限个新的Actor | |||
3. 为下一个接收的消息指定行为 | |||
以上三种动作并没有固定的顺序,可以并发地执行。Actor会根据接收到的消息进行不同的处理。 | |||
简而言之: 一个Actor指的是一个最基本的计算单元,它能接收一个消息并且基于其执行计算。 | |||
综上,我们知道可以把系统中的所有事物都抽象成一个Actor,那么在一个系统中,可以将一个大规模的任务分解为一些小任务,这些小任务 | |||
可以由多个Actor并发处理,从而减少任务的完成时间和任务复杂度。 | |||
为什么会在讲Erlang进程的时候讲Actor模型的概念,就是因为对于Erlang的并发编程模型正是基于Actor模型,Erlang的代码运行在 | |||
进程中,而进程就是Erlang称呼Actor的方式,Eralng也是最著名的使用Actor规则的编程的语言。 | |||
### Eralng进程特性 | |||
在Erlang的进程不是我们传统上的进程,Erlang进程是轻量级进程,它的生成、上下文切换和消息传递是由虚拟机管理的,操作系统 | |||
线程进程和Erlang进程之间没有任何联系,这使并发有关的操作不仅独立于底层的操作系统,而且也是非常高效和具有很强可扩展性。 | |||
它运行在 Erlang 虚拟机上,非常小,非常轻,可以瞬间创建上万,甚至几十万个,进程间完全是独立的内存空间执行,不共享内存, | |||
这些独立的内存空间可以独立的进行垃圾回收,基于独立运行,在发生错误的时候也是隔离的,其他不相关的进程可以继续运行。 | |||
在进程运行时若出现错误,由于进程的轻量级,Erlang 采取的措施是“任其崩溃”和“让其他进程修复”。 | |||
在Erlang上查看默认限制数量是26万多,可以进行修改。每个进程创建后都会有一个独一无二的 Pid,这些进程之间通过 Pid 来互相发 | |||
送消息,进程的唯一交互方式也是消息传递,消息也许能被对方收到,也许不能,收到后可以处理该消息。消息发送是异步的如果想知道某 | |||
个消息是否被进程收到,必须向该进程发送一个消息并等待回复。 | |||
## 进程创建 | |||
Erlang 中的并发编程只需要如下几个简单的函数。 | |||
```erlang | |||
Pid = spawn(Mod,Func, Args) | |||
``` | |||
创建一个新的并发进程来执行Mod模块中的 Fun(),Args 是参数。 | |||
跟上面提供的spawn/3功能相同的函数还有: | |||
```erlang | |||
spawn(Fun) -> pid() | |||
spawn(Node, Fun) -> pid() | |||
spawn(Module, Function, Args) -> pid() | |||
spawn(Node, Module, Function, Args) -> pid() | |||
spawn_link(Fun) -> pid() | |||
spawn_link(Node, Fun) -> pid() | |||
spawn_link(Module, Function, Args) -> pid() | |||
spawn_link(Node, Module, Function, Args) -> pid() | |||
spawn_monitor(Fun) -> {pid(), reference()} | |||
spawn_monitor(Module, Function, Args) -> {pid(), reference()} | |||
spawn_opt(Fun, Options) -> pid() | {pid(), reference()} | |||
spawn_opt(Node, Fun, Options) -> pid() | {pid(), reference()} | |||
spawn_opt(Module, Function, Args, Options) ->pid() | {pid(), reference()} | |||
spawn_opt(Node, Module, Function, Args, Options) ->pid() | {pid(), reference()} | |||
``` | |||
创建好进程,返回对应的Pid之后向就可以向进程进程发送消息,erlang用 “!”来发送消息,格式如下。notice:消息发送是异步的, | |||
发送方不等待而是继续之前的工作。 | |||
```erlang | |||
Pid !Message, | |||
Pid1 ! Pid2 ! Pid3 ! Pid..n ! Message. | |||
``` | |||
erlang用 receve ... end 来接受发送给某个进程的消息,匹配后处理,格式如下。 | |||
```erlang | |||
receive | |||
Pattern1 [when Guard1] -> | |||
Expression1; | |||
Pattern2 [when Guard2] -> | |||
Expression2; | |||
... | |||
after T -> | |||
ExpressionTimeout | |||
end | |||
``` | |||
某个消息到达后,会先与 Pattern 进行匹配,匹配相同后执行,若未匹配成功消息则会保存起来待以后处理,进程会开始下一轮操作, | |||
若等待超时T,则会执行表达式 ExpressionTimeout。 | |||
## 进程注册与监控 | |||
### 进程注册 | |||
有些时候使用通过进程Pid来标识进程需要维护进程Pid,出于某些原因维护进程Pid,不方便灵活,比如你给某个服务器进程请求数据, | |||
你还得考虑怎么得到服务器进程的Pid,有些时候进程由于某种异常重启后Pid会发生变化,如果没有及时同步机制,会导致功能异常, | |||
于是乎Erlang提供了一套进程注册管理的机制----注册进程Erlang中管理注册进程的有4个内置函数,register、unregister、 | |||
whereis、registered,它们的用法如下: | |||
1)register(Atom, Pid):将一个进程Pid注册一个名为AnAtom的原子,如果原子AnAtom已经被另一个注册进程所使用, | |||
那么注册就会失败。 | |||
2)unregister(Atom):移除与AnAtom相对应进程的所有注册信息。如果一个注册死亡,那么它也会被自动取消注册。 | |||
3)whereis(Atom) -> Pid | undefined:判断AnAtom是否已经被其他进程注册。如果成功,则返回进程标识符Pid。 | |||
如果AnAtom没有与之相对应的进程,那么就返回原子undefined。 | |||
4)registered() -> [AnAtom ::atom()]:返回一个系统中所有已经注册的名称列表。 | |||
### 进程监控 | |||
Erlang 对于进程处理理念之一是“任其崩溃”和“让其他进程修复”,常规Erlang系统中有很多进程同时运行,进程之间可能相互依赖, | |||
这么复杂的情况之下怎么实现该理念呢?Erlang除了提供exception,try catch等语法,还支持Link和Monitor两种监控进程的机制, | |||
使得所有进程可以连接起来,组成一个整体。当某个进程出错退出时,其他进程都会收到该进程退出的消息通知。有了这些特点,使用erlang | |||
建立一个简单,并且健壮的系统就不是什么难事。 | |||
#### 进程双向监控-Link | |||
相关API link(Pid), A进程调用了link(Pid) 则A进程与Pid之间就建立起了双向连接,如果两个进程相连接,如果其中一个终止时, | |||
讲发送exit信号给另一方,使其终止,同时终止进程会依次发送exit信号给所有与其连接的进程,这使得exit信号在系统内层层蔓延。 | |||
该函数连接不存在的进程时会导致发起连接的进程终止 | |||
spawn_link()系列函数 它与link(Pid)的差别就是 原子性与非原子性 | |||
unlink(Pid) 移除调用进程与Pid的连接 | |||
通过调用process_flag(trap_exit, true)可以设置捕捉exit信号, | |||
假如有A,B两个进程且彼此link | |||
总结... | |||
1.当A的结束原因是normal时(进程正常执行完就是normal),B是不会退出的,此时link机制不发生作用 | |||
2.若A的结束原因是killed,例如调用exit(PidA,kill) ,则无论B是否有设置trap_exit,B都会terminate,此时退出信号捕捉机制是无效的 | |||
3.若A的结束原因不是normal也不是killed(例如exit(PidA,Reason)),那么B在设置了trap_exit时,会捕捉到退出信号, | |||
取而代之的是收取到一条消息{‘EXIT’,Pid,Reason},这时B不会结束,用户可以根据收到的消息对A进程的结束进行处理;若B没有设置trap_exit,B就会terminate | |||
|捕获状态 |退出信号(原因) |动作 | | |||
| :-------------------| ------------------: | :--------------------------------------:| | |||
|false | normal | 不做任何事 | | |||
|false | kill | 消亡,向链接的进程广播退出信号(killed) | | |||
|false | X | 消亡,向链接的进程广播退出信号X | | |||
|true | normal | 接收到{'EXIT', Pid, nomal} | | |||
|true | kill | 消亡,向链接的进程广播退出信号(killed) | | |||
|true | X | 将{'EXIT', Pid, X} 加入到邮箱 | | |||
#### 监视器(monitor) | |||
相关API | |||
monitor(process, monitor_process_identifier()) %monitor_process_identifier() 为Pid或者已注册的进程名称 | |||
demonitor(MonitorRef) | |||
demonitor(MonitorRef, OptionList) | |||
监视器与link不同的是它是单向式观察一些进程终止,各个监视器通过Erlang的引用相互区分,是调用monitor返回的,具有唯一性, | |||
而且A进程可以设置多个对B进程的监视器,每一个通过不同的引用区分。 | |||
当被监视的进程终止时,一条格式{'Down',Reference, process, Pid, Reason}的消息会被发给监视此进程的进程 | |||
调用erlang:demonitor(Reference)可以移除监视器, | |||
调用erlang:demonitor(Reference,[flush])可以让该监视进程邮箱中所有与Reference对应的{'DOWN', Reference,process,Pid,Reason} | |||
的消息被冲刷掉。 | |||
如果尝试监视一个不存在的进程会导致收到一条{'DOWN', Reference, process, Pid,Reason}的消息,其中Reason为noproc,这和link()不一样 | |||
## 进程调度 | |||
就目前计算机体系结构而言,任何进程或线程要执行就需要得到CPU资源,对于erlang的进程同样如此。erlang虚拟机同时存在成千上万的进程, | |||
但是cpu核心数又是有限的,所有erlang并发特性就需要一个合适的调度规则来安排各个进程的运行, | |||
简单而言,erlang虚拟机调度程序保留两个队列,准备好运行的就绪队列以及等待接收消息的进程的等待队列。当等待队列中的进程收到消息或获 | |||
得超时时,它将被移动到就绪队列。调度程序从就绪队列中选择第一个进程并将其交给BEAM执行一个时间片。当时间片用完时,BEAM会抢占正在 | |||
运行的进程,并将进程添加到就绪队列的末尾。如果在时间片用完之前在接收中阻止了进程,则会将其添加到等待队列中。 | |||
Erlang调度器主要有以下特点: | |||
1. 进程调度运行在用户空间 :Erlang进程不同于操作系统进程,Erlang的进程调度也跟操作系统完全没有关系,是由Erlang虚拟机来完成的; | |||
2. 调度是抢占式的:每一个进程在创建时,都会分配一个固定数目的reduction(这个数量默认值是2000),每一次操作(函数调用), | |||
reduction就会减少,当这个数量减少到0时或者进程没有匹配的消息时,抢占就会发生(无视优先级); | |||
3. 每个进程公平的使用CPU:每个进程分配相同数量的reduction,可以保证进程可以公平的(不是相等的)使用CPU资源 | |||
4. 调度器保证软实时性:Erlang中的进程有优先级,调度器可以保证在下一次调度发生时,高优先级的进程可以优先得到执行。 | |||
Reduction | |||
受操作系统中基于时间片调度算法的影响,一开始知道有reduction这个概念时,一直想搞清楚这个reduction到底对应多长的绝对时间,不过, | |||
从Erlang本身对reduction的使用来看,完全没有必要纠结这个问题。《Erlang编程指南》一书中对reduction的说明如下: | |||
程序中的每一个命令,无论它是一个函数调用,还是一个算术操作,或者内置函数,都会分配一定数量的reduction。虚拟机使用这个值来衡量一个 | |||
进程的活动水平。 | |||
进程优先级 | |||
Erlang进程有四种优先级:max, high, normal, low(max只在Erlang运行时系统内部使用,普通进程不能使用)。Erlang运行时有两个 | |||
运行队列对应着max和high优先级的运行任务,normal和low在同一个队列中。调度器在调度发生时,总是首先查看具体max优先级的进程队列, | |||
如果队列中有可以进行的进程,就会运行,直到这个队列为空。然后会对high优先级的进程队列做同样的操作(在SMP环境,因为同时有几个调度器,所以在同一时间,可能会有不同优先级的任务在同时运行; | |||
但在同一个调度器中,同一时间,肯定是高优先级的任务优先运行)。普通进程在创建时,一般是normal优先级。normal和low优先级的进程只有 | |||
在系统中没有max和high优先级的进程可运行时才会被调度到。通常情况下,normal和low优先级的进程交替执行,low优先级获得CPU资源相对 | |||
更少(一般情况下):low优先级的任务只有在运行了normal优先级任务特定次数后(在R15B中,这个数字是8)才会被调度到(也就是说只有 | |||
在调度了8个normal优先级的进程后,low优先级的进程才会被调度到,即使low优先级的进程比normal优先级的进程更早进入调度队列,这种 | |||
机制可能会引起优先级反转:假如你有成千上万的活动normal进程,而只有几个low优先级进程,那么相比normal进程,low优先级可能会获得 | |||
更多的CPU资源)。 | |||
## 进程发送消息 | |||
Erlang系统中,进程之间的通信是通过消息传递来完成的。消息使用Pid ! Message的形式发送,通过receive语句获取。每个Erlang进程 | |||
都有用来存储传入消息的信箱。当一个消息发送的时候,它会从发送进程中拷贝到接收进程的信箱,并以它们到达的时间次序存储。消息的传递是 | |||
异步的,一个发送进程不会在发送消息后被暂停。 | |||
上面提到发送消息时,会在两个进程之间存在消息复制,为什么需要复制呢?这就跟进程的堆内存有关。虽然在Erlang的文档(heap_type)中 | |||
说明堆内存有三种类型:private,shared,hybrid,但是在实际的代码中,只有两种private和hybrid | |||
(参见[$R15B_OTP_SRC/erts/emulator/beam/erl_bif_info.c --> system_info_1]), | |||
(参见[$R15B_OTP_SRC/erts/Makefile.in:# Until hybrid is nofrag, don't build it.), | |||
也就是说Erlang目前的堆内存只有一种:private。 | |||
private类型的堆内存是跟shared类型相对的:shared是指所有线程共享同一块内存(比如Java),多个线程对同一块内存的访问需要锁保护; | |||
而private类型的堆内存是指每个进程独享一块内存,对于内存的访问不需要锁保护。 | |||
在Erlang的private堆内存架构下,发送消息需要做三件事件: | |||
1. 计算消息的大小,并在接收进程的内存空间中给消息分配内存; | |||
2. 将消息的内容拷贝到接收进程的堆内存中; | |||
3. 最后将消息的地址添加到接收进程的消息队列。 | |||
从上面的步骤可以看出,拷贝消息的代码是O(n),n是消息的长度,也就是说消息越长,花费越大。所以在使用Erlang时,要避免大数据量的大消息传递。 | |||
在shared堆内存架构下,发送消息只需要O(1)(只传递消息地址),那为什么Erlang要默认选择private类型的堆内存呢? | |||
其实这跟后面要讲到的Erlang的GC相关:private的优势就是GC的延迟很低,可以很快的完成(因为只保存一个进程的数据, | |||
GC扫描时的数据量很小)。在SMP环境下,实际上每个进程有两个消息队列。进程发送消息时,实际上消息是添加到目标进程的公有队列 | |||
(通过锁来保证互斥访问);而目标进程在消费消息时,实际上是在自己的私有消息队列上处理的,从而减小锁带来的访问开销。但是, | |||
如果目标进程在自己的私有消息队列上无法匹配到消息,那么公有队列中的消息将被添加到私有队列。 | |||
## 进程接收消息 | |||
```erlang | |||
receive | |||
Pattern1 [when Guard1] -> | |||
Expression1; | |||
Pattern2 [when Guard2] -> | |||
Expression2; | |||
... | |||
after T -> | |||
ExpressionTimeout | |||
end | |||
``` | |||
整个过程如下 | |||
1. 当我们输入receive语句时,我们启动一个计时器(如果有after T)。 | |||
2. 获取邮箱中的第一个消息,并尝试将其与Pattern1、Pattern2等进行匹配。 | |||
如果匹配成功,则从邮箱中删除消息,并计算模式后面的表达式。 | |||
3. 如果receive语句中的任何模式都不匹配邮箱中的第一个消息,那么第一个消息将从邮箱中删除并放入“save队列”中。 | |||
然后尝试邮箱中的第二条消息。重复此过程,直到找到匹配的消息或检查邮箱中的所有消息为止。 | |||
4. 如果邮箱中的所有消息都不匹配,则进程将被挂起,并在下次将新消息放入邮箱时重新安排执行时间。注意,当新消息到达时, | |||
保存队列中的消息不会重新匹配;只匹配新消息( Erlang的实现是非常“聪明”的,并且能够最小化每个消息被接收方的receive测试的次数) | |||
5. 一旦匹配了消息,那么所有放入save队列的消息都将按照到达进程的顺序重新进入邮箱。如果设置了计时器, | |||
则清除计时器。 | |||
6. 如果计时器在等待消息时超时,则计算表达式ExpressionsTimeout,并按到达进程的顺序将任何保存的消息放回邮箱。 | |||
## 进程GC | |||
erlang 进程GC | |||
Memory Layout 内存分布 | |||
在我们深入垃圾回收机制之前,我们先来看看Erlang进程的内存布局. 一个Erlang进程的内存布局通常分为是三个部分(有人认为是四个部分, | |||
把mailbox作为单独的一个部分), 进程控制块, 堆和栈,和普通的Linux进程的内存布局非常类似. | |||
``` | |||
Shared Heap Erlang Process Memory Layout | |||
+----------------------------------+ +----------------------------------+ | |||
| | | | | |||
| | | PID / Status / Registered Name | Process | |||
| | | | Control | |||
| | | Initial Call / Current Call +----> Block | |||
| | | | (PCB) | |||
| | | Mailbox Pointers | | |||
| | | | | |||
| | +----------------------------------+ | |||
| | | | | |||
| | | Function Parameters | | |||
| | | | Process | |||
| | | Return Addresses +----> Stack | |||
| | | | | |||
| +--------------+ | | Local Variables | | |||
| | | | | | | |||
| | +------------+--+ | +-------------------------------+--+ | |||
| | | | | | | | | |||
| | | +-------------+--+ | | ^ v +----> Free | |||
| | | | | | | | | Space | |||
| | | | +--------------+-+ | +--+-------------------------------+ | |||
| +-+ | | | | | | | |||
| +-+ | Refc Binary | | | Mailbox Messages (Linked List) | | |||
| +-+ | | | | | |||
| +------^---------+ | | Compound Terms (List, Tuples) | Process | |||
| | | | +----> Private | |||
| | | | Terms Larger than a word | Heap | |||
| | | | | | |||
| +--+ ProcBin +-------------+ Pointers to Large Binaries | | |||
| | | | | |||
+----------------------------------+ +----------------------------------+ | |||
``` | |||
进程控制块: 进程控制块持有关于进程的一些信息, 比如PID, 进程状态(running, waitting), 进程注册名, 初始和当前调用, | |||
指向进程mailbox的指针 | |||
栈: 栈是向下增长的, 栈持有函数调用参数,函数返回地址,本地变量以及一些临时空间用来计算表达式. | |||
堆: 堆是向上增长的, 堆持有进程的mailbox, 复合terms(Lists, Tuples, Binaries),以及大于一个机器字的对象(比如浮点数对象). | |||
大于64个字节的二进制terms,被称为Reference Counted Binary, 他们不是存在进程私有堆里面,他们是存在一个大的共享堆里,所有进程 | |||
都可以通过指向RefC Binary的指针来访问该共享堆,RefC Binary指针本身是存在进程私有堆里面的. | |||
GC Details | |||
为了更准确的解释默认的Erlang垃圾回收机制, 实际上运行在每个独立Erlang进程内部的是分代拷贝垃圾回收机制, 还有一个引用计数的 | |||
垃圾回收运行在共享堆上. | |||
Private Heap GC 私有堆垃圾回收 | |||
私有堆的垃圾回收是分代的. 分代机制把进程的堆内存分为两个部分,年轻代和年老代. 区分是基于这样一个考虑, 如果一个对象在运行 | |||
一次垃圾回收之后没有被回收,那么这个对象短期内被回收的可能性就很低. 所以, 年轻代就用来存储新分配的数据,年老代就用来存放运行 | |||
一定次数的垃圾回收之后依然幸存的数据. 这样的区分可以帮助GC减少对那些很可能还不是垃圾的数据不必要的扫描. 对应于此, Erlang的 | |||
GC扫描有两个策略, Generational(Minor) 和 Fullsweep(Major). Generational GC只回收年轻代的区域, 而Fullsweep则同时回收年轻代和 | |||
年老代. | |||
下面我们一起来review一下一个新创建的Erlang进程触发GC的步骤, 假设以下不同的场景: | |||
场景 1: | |||
Spawn > No GC > Terminate | |||
假设一个生存期较短的进程, 在存活期间使用的堆内存也没有超过 min_heap_size,那么在进程结束是全部内存即被回收. | |||
场景 2: | |||
Spawn > Fullsweep > Generational > Terminate | |||
假设一个新创建的进程,当进程的数据增长超过了min_heap_size时, fullsweep GC即被触发, 因为在此之前还没有任何GC被触发,所以堆区 | |||
还没有被分成年轻代和年老代. 在第一次fullsweep GC结束以后, 堆区就会被分为年轻代和年老代了, 从这个时候起, GC的策略就被切换为 | |||
generational GC了, 直到进程结束. | |||
场景 3: | |||
Spawn > Fullsweep > Generational > Fullsweep > Generational > ... > Terminate | |||
在某些情景下, GC策略会从generation再切换回fullsweep. 一种情景是, 在运行了一定次数(fullsweep_after)的genereration GC之后, | |||
系统会再次切换回fullsweep. 这个参数fullsweep_after可以是全局的也可以是单进程的. 全局的值可以通过函数erlang:system_info(fullsweep_after)获取, | |||
进程的可以通过函数erlang:process_info(self(),garbage_collection)来获取. 另外一种情景是, 当generation GC(minor GC)不能够收集到足够的内存空间时. | |||
最后一种情况是, 当手动调用函数garbage_collector(PID)时. 在运行fullsweep之后, GC策略再次切换回generation GC直到以上的任意一个情景再次出现. | |||
场景 4: | |||
Spawn > Fullsweep > Generational > Fullsweep > Increase Heap > Fullsweep > ... > Terminate | |||
假设在场景3里面,第二个fullsweep GC依然没有回收到足够的内存, 那么系统就会为进程增加堆内存, 然后该进程就回到第一个场景,像刚创建的进程一样首先 | |||
开始一个fullsweep,然后循环往复. | |||
那么对Erlang来说, 既然这些垃圾回收机制都是自动完成的, 为什么我们需要花时间去了解学习呢? 首先, 通过调整GC的策略可以使你的系统运行的更快. 其次, | |||
了解GC可以帮助我们从GC的角度来理解为什么Erlang是一个软实时的系统平台. 因为每个进程有自己的私有内存空间和私有GC,所以每次GC发生的时候只在进程 | |||
内部进行,只stop本进程, 不会stop其他进程,这正是一个软实时系统所需要的. | |||
Shared Heap GC 共享堆垃圾回收 | |||
共享堆的GC是通过引用计数来实现的. 共享堆里面的每个对象都有一个引用计数,这个计数就是表示该对象被多少个Erlang进程持有(对象的指针存在进程的私有堆里). | |||
如果一个对象的引入计数变成0的时候就表示该对象不可访问可以被回收了. | |||
进程调度 | |||
## ## 进程调度 | |||
就目前计算机体系结构而言,任何进程或线程要执行就需要得到CPU资源,对于erlang的进程同样如此。erlang虚拟机同时存在成千上万的进程, | |||
但是cpu核心数又是有限的,所有erlang并发特性就需要一个合适的调度规则来安排各个进程的运行, | |||
简单而言,erlang虚拟机调度程序保留两个队列,准备好运行的就绪队列以及等待接收消息的进程的等待队列。当等待队列中的进程收到消息或获 | |||
得超时时,它将被移动到就绪队列。调度程序从就绪队列中选择第一个进程并将其交给BEAM执行一个时间片。当时间片用完时,BEAM会抢占正在 | |||
运行的进程,并将进程添加到就绪队列的末尾。如果在时间片用完之前在接收中阻止了进程,则会将其添加到等待队列中。 | |||
Erlang调度器主要有以下特点: | |||
1. 进程调度运行在用户空间 :Erlang进程不同于操作系统进程,Erlang的进程调度也跟操作系统完全没有关系,是由Erlang虚拟机来完成的; | |||
2. 调度是抢占式的:每一个进程在创建时,都会分配一个固定数目的reduction(这个数量默认值是2000),每一次操作(函数调用), | |||
reduction就会减少,当这个数量减少到0时或者进程没有匹配的消息时,抢占就会发生(无视优先级); | |||
3. 每个进程公平的使用CPU:每个进程分配相同数量的reduction,可以保证进程可以公平的(不是相等的)使用CPU资源 | |||
4. 调度器保证软实时性:Erlang中的进程有优先级,调度器可以保证在下一次调度发生时,高优先级的进程可以优先得到执行。 | |||
1. What operators does Erlang have? | |||
``` | |||
Arithmetic operators: + - * / div rem | |||
Comparison operators: =:= == =/= /= > >= < =< | |||
Logical operators: and andalso or orelse | |||
Bitwise operators: bsl bsr Bitwise logical operators: band Bor bxor bnot | |||
``` | |||
--------------------- | |||
@ -1,333 +0,0 @@ | |||
节点连接 | |||
分布式erlang系统中的节点是松散连接的, 第一次使用另一个节点名称 例如调用 spawn(Node, M, F, A)或者 | |||
net_adm:ping(Node)的时候 就会尝试连接该节点 | |||
默认情况下 节点连接是可以传递的 如果节点A连接了节点B 节点B连接了节点C 则节点A会尝试连接到节点C | |||
可以通过命令 `-connect_all false` 来关闭这个功能 | |||
如果想主动断开与某个节点的连接 可以使用 `erlang:disconnect_node(Node)` 强制断开节点连接 | |||
Erlang Port Mapper Daemon epmd会在启动Erlang节点的每个主机上自动启动。它负责将符号节点名映射到机器地址。请参见ERTS中的 epmd(1)手册页。 | |||
四、跨机器连通防火墙问题 | |||
要想连通某个节点,该节点(即被连接的)要保证: | |||
1. epmd的端口(默认是4369)在防火墙打开; | |||
2. erl要加 `-kernel inet_dist_listen_min Min inet_dist_listen_max Max` 设定使用的端口范围(若只有一个端口,则Min==Max),要保证这些端口在防火墙打开,并且这些端口不能全部被占用 | |||
也就是要连接某个节点,是和该节点所在机器的epmd以及该节点通讯。所以发起连接的节点不需要上面的2个要求,即所在机器不需要防火墙打开4369端口,也不需要加-kernel inet_dist_listen_min Min inet_dist_listen_max Max | |||
隐藏节点 在分布式erlang系统中 有时候连接到所有节点是不好的 可以用使用 命令行标记 `-hidden` 隐藏节点和其他节点的连接是不可传递的 同样隐藏节点也不会显示在nodes()函数返回的节点列表 | |||
这也意味这未将隐藏节点添加到global跟踪的节点集群中,另外`nodes(hidden) or nodes(connected)` 会返回隐藏的节点 | |||
节点cookie | |||
身份验证确定允许哪些节点相互通信。在不同Erlang节点的网络中,它以最低的级别内置到系统中。每个节点都有自己的cookie,它是一个Erlang原子。 | |||
当一个节点尝试连接到另一个节点时,将对魔术cookie进行比较。如果它们不匹配,则连接的节点拒绝连接。 | |||
可以使用 `erlang:set_cookie(node(), Cookie)` 将本地节点的Cookie设置为Cookie, `erlang:get_cookie()` 返回本地节点的cookie | |||
为了使cookie为Cookie1的节点Node1能够连接到具有不同cookie Cookie2的节点Node2或者让Node2接收到Node1的连接,必须首先在Node1处调用 | |||
`erlang:set_cookie(Node2, Cookie2)`(该调用不会修改 Node1本地的cookie, 而且这样操作之后 Node1会自动与Node2所连节点列表中的节点cookie都为Cookie1的节点互联,不相同的cookie不会自动互联) | |||
这样具有多个cookie的分布式系统就可以互联了 | |||
关于分布式的BIFS | |||
Some useful BIFs for distributed programming (for more information, see the erlang(3) manual page in ERTS: | |||
BIF Description | |||
erlang:disconnect_node(Node) Forces the disconnection of a node. | |||
erlang:get_cookie() Returns the magic cookie of the current node. | |||
is_alive() Returns true if the runtime system is a node and can connect to other nodes, false otherwise. | |||
monitor_node(Node, true|false) Monitors the status of Node. A message{nodedown, Node} is received if the connection to it is lost. | |||
node() Returns the name of the current node. Allowed in guards. | |||
node(Arg) Returns the node where Arg, a pid, reference, or port, is located. | |||
nodes() Returns a list of all visible nodes this node is connected to. | |||
nodes(Arg) Depending on Arg, this function can return a list not only of visible nodes, but also hidden nodes and previously known nodes, and so on. | |||
erlang:set_cookie(Node, Cookie) Sets the magic cookie used when connecting to Node. If Node is the current node, Cookie is used when connecting to all new nodes. | |||
spawn[_link|_opt](Node, Fun) Creates a process at a remote node. | |||
spawn[_link|opt](Node, Module, FunctionName, Args) Creates a process at a remote node. | |||
Distribution Command-Line Flags | |||
Examples of command-line flags used for distributed programming (for more information, see the erl(1) manual page in ERTS: | |||
Command-Line Flag Description | |||
-connect_all false Only explicit connection set-ups are used. | |||
-hidden Makes a node into a hidden node. | |||
-name Name Makes a runtime system into a node, using long node names. | |||
-setcookie Cookie Same as calling erlang:set_cookie(node(), Cookie). | |||
-sname Name Makes a runtime system into a node, using short node names. | |||
Distribution Modules Examples of modules useful for distributed programming: | |||
In the Kernel application: | |||
Module Description | |||
global A global name registration facility. | |||
global_group Grouping nodes to global name registration groups. | |||
net_adm Various Erlang net administration routines. | |||
net_kernel Erlang networking kernel. | |||
Kernel Modules Useful For Distribution. In the STDLIB application: | |||
Module Description | |||
slave Start and control of slave nodes. | |||
%% ***************************************** net_adm 模块 ********************************************** | |||
## dns_hostname(Host) -> {ok, Name} | {error, Host} | |||
Types | |||
Host = atom() | string() | |||
Name = string() | |||
返回的正式名称主机,或 {错误,主机}如果没有这样的名字中找到 | |||
## host_file() -> Hosts | {error, Reason} | |||
Types | |||
Hosts = [Host :: atom()] | |||
Reason = | |||
file:posix() | | |||
badarg | terminated | system_limit | | |||
{Line :: integer(), Mod :: module(), Term :: term()} | |||
读取文件.hosts.erlang,请参阅文件部分 。以列表形式返回此文件中的主机。如果无法读取文件或无法解释文件上的Erlang术语,则返回{error,Reason}。 | |||
## localhost() -> Name | |||
Types | |||
Name = string() | |||
返回本地主机的名称。如果Erlang以命令行标志-name开头,则Name是标准名称。 | |||
## names() -> {ok, [{Name, Port}]} | {error, Reason} | |||
## names(Host) -> {ok, [{Name, Port}]} | {error, Reason} | |||
Types | |||
Host = atom() | string() | inet:ip_address() | |||
Name = string() | |||
Port = integer() >= 0 | |||
Reason = address | file:posix() | |||
ie. | |||
(arne@dunn)1> net_adm:names(). | |||
{ok,[{"arne",40262}]} | |||
与epmd -names类似,请参阅 erts:epmd(1)。 主机默认为本地主机。返回epmd在指定主机上注册的Erlang节点的名称和关联的端口号 。如果epmd无法运行,则返回 {error, address}。 | |||
## ping(Node) -> pong | pang | |||
Types | |||
Node = atom() | |||
Sets up a connection to Node. Returns pong if it is successful, otherwise pang. | |||
## world() -> [node()] | |||
## world(Arg) -> [node()] | |||
Types | |||
Arg = verbosity() | |||
verbosity() = silent | verbose | |||
调用Erlang主机文件.hosts.erlang中指定的所有主机的names(Host),收集答复,然后在所有这些节点上评估ping(Node)。返回已成功ping通的所有节点的列表。 | |||
Arg默认为silent。如果Arg == verbose,则该函数将写入有关将其ping到标准输出的节点的信息。 | |||
当启动一个节点并且最初不知道其他网络节点的名称时,此功能很有用。 | |||
Returns {error, Reason} if host_file() returns {error, Reason}. | |||
## world_list(Hosts) -> [node()] | |||
## world_list(Hosts, Arg) -> [node()] | |||
Types | |||
Hosts = [atom()] | |||
Arg = verbosity() | |||
verbosity() = silent | verbose | |||
Same as world/0,1, but the hosts are specified as argument instead of being read from .hosts.erlang. | |||
## .hosts.erlang | |||
文件.hosts.erlang由许多以Erlang术语编写的主机名组成。在当前工作目录,用户的主目录和$OTP_ROOT (Erlang / OTP的根目录)中依次查找。 | |||
文件.hosts.erlang的格式必须是每行一个主机名。主机名必须用引号引起来。 | |||
example | |||
'super.eua.ericsson.se'. | |||
'renat.eua.ericsson.se'. | |||
'grouse.eua.ericsson.se'. | |||
'gauffin1.eua.ericsson.se'. | |||
^ (new line) | |||
%% ***************************************** net_kernel 模块 ********************************************** | |||
## 描述 | |||
网络内核是注册为net_kernel的系统进程, 必须运行才能使分布式Erlang正常工作。该过程的目的是实现BIF的部分spawn / 4和spawn_link / 4并提供对网络的监视。 | |||
使用命令行标志-name或-sname启动一个Erlang节点 : | |||
$ erl -sname foobar | |||
也可以 直接从普通的Erlang Shell提示符下调用net_kernel:start([foobar]): | |||
1> net_kernel:start([[foobar, shortnames])。 | |||
{ok,<0.64.0>} | |||
(foobar @ gringotts)2> | |||
如果节点以命令行标志-sname开头,则节点名称为foobar @ Host,其中Host是主机的简称(不是完全限定的域名)。如果以flag -name开头,则节点名称为foobar @ Host,其中Host是标准域名。有关更多信息,请参见 erl。 | |||
通常,引用另一个节点时会自动建立连接。可以通过将内核配置参数dist_auto_connect设置为never来禁用此功能 ,请参阅 kernel(6)。在这种情况下,必须通过调用connect_node / 1显式建立连接 。 | |||
## allow(Nodes) -> ok | error | |||
Types | |||
Nodes = [node()] | |||
允许访问指定的节点集。 | |||
在第一次调用allow / 1之前,可以连接具有正确cookie的任何节点。当允许/ 1被调用,建立允许节点列表。从(或到)不在该列表中的节点进行的任何访问尝试都将被拒绝。 | |||
随后对allow / 1的调用会将指定的节点添加到允许的节点列表中。无法从列表中删除节点。 | |||
如果Nodes中的任何元素都不是原子,则返回错误。 | |||
## connect_node(Node) -> boolean() | ignored | |||
Types | |||
Node = node() | |||
建立与Node的连接。如果已建立连接或已经建立连接,或者Node是本地节点本身,则返回 true。如果连接尝试失败,则返回false;如果本地节点未处于活动状态, 则将其忽略。 | |||
## get_net_ticktime() -> Res | |||
Types | |||
Res = NetTicktime | {ongoing_change_to, NetTicktime} | ignored | |||
NetTicktime = integer() >= 1 | |||
获取net_ticktime(请参阅 kernel(6))。 | |||
定义的返回值(Res): | |||
NetTicktime | |||
net_ticktime is NetTicktime seconds.。 | |||
{ongoing_change_to,NetTicktime} | |||
net_kernel is currently changing net_ticktime to NetTicktime seconds. | |||
ignored | |||
The local node is not alive. | |||
##getopts(Node, Options) -> {ok, OptionValues} | {error, Reason} | ignored | |||
Types | |||
Node = node() | |||
Options = [inet:socket_getopt()] | |||
OptionValues = [inet:socket_setopt()] | |||
Reason = inet:posix() | noconnection | |||
获取连接到Node的配电插座的一个或多个选项。 | |||
如果Node是连接的节点,则返回值与inet:getopts(Sock,Options) 中的返回值相同 ,其中Sock是Node的分发套接字。 | |||
返回忽略,如果本地节点是不是活的或 {错误,noconnection}如果节点未连接。 | |||
## monitor_nodes(Flag) -> ok | Error | |||
## monitor_nodes(Flag, Options) -> ok | Error | |||
Types | |||
Flag = boolean() | |||
Options = [Option] | |||
Option = {node_type, NodeType} | nodedown_reason | |||
NodeType = visible | hidden | all | |||
Error = error | {error, term()} | |||
调用过程订阅或取消订阅节点状态更改消息。当新的节点连接时nodeup消息,一个节点断开时nodedown消息被传递到所有订阅的进程 | |||
如果Flag为true,则开始新的订阅。如果Flag为false,则将 停止所有使用相同选项启动的先前订阅。如果两个选项列表包含相同的选项集,则认为它们是相同的。 | |||
从内核版本2.11.4和ERTS版本5.5.4开始,保证以下内容: | |||
在从远程节点传递通过新建立的连接传递的任何消息之前,先传递nodeup消息。 | |||
直到已传递了来自远程节点的通过连接传递的所有消息后,才会传递nodedown消息。 | |||
从内核2.13版和ERTS 5.7版开始,保证以下内容: | |||
在erlang:nodes / X结果中出现相应节点后,将传递nodeup消息 。 | |||
在erlang:nodes / X的结果中对应的节点消失之后,将传递nodedown消息 。 | |||
节点状态更改消息的格式取决于 Options。如果Options为 [],这是默认设置,则格式如下: | |||
{nodeup,Node} | {nodedown,Node} Node= node() | |||
如果Options不是[],则格式如下: | |||
{nodeup,Node,InfoList} | {nodedown,Node,InfoList} Node= node() InfoList = [{Tag,Val}] | |||
InfoList是一个元组列表。其内容取决于 Options,请参见下文。 | |||
另外,当OptionList == []时,仅监视可见节点,即出现在erlang:nodes / 0结果中的 节点。 | |||
选项可以是以下任意一项: | |||
{node_type,NodeType} NodeType的有效值: | |||
visible | |||
订阅仅针对可见节点的节点状态更改消息。元组{node_type,visible}包含在InfoList中。 | |||
hidden | |||
订阅仅针对隐藏节点的节点状态更改消息。元组{node_type,hidden}已包含在InfoList中。 | |||
all | |||
订阅可见和隐藏节点的节点状态更改消息。元组 {node_type,visible | hidden}已包含在 InfoList中。 | |||
nodedown_reason | |||
元组{nodedown_reason,Reason}包含 在nodedown消息的InfoList中。 | |||
原因可以取决于所使用的分发模块或进程是任何术语,但是对于标准TCP分发模块,可以是以下任意一种: | |||
connection_setup_failed | |||
连接设置失败( 发送nodeup消息后)。 | |||
no_network | |||
没有可用的网络。 | |||
net_kernel_terminated | |||
所述net_kernel过程终止。 | |||
shutdown | |||
未指定的连接关闭。 | |||
connection_closed | |||
连接已关闭。 | |||
disconnect | |||
连接已断开连接(从当前节点强制连接)。 | |||
net_tick_timeout | |||
Net tick time-out. | |||
send_net_tick_failed | |||
Failed to send net tick over the connection. | |||
get_status_failed | |||
从保持连接的端口检索状态信息失败。 | |||
## set_net_ticktime(NetTicktime)-> Res | |||
## set_net_ticktime(NetTicktime,TransitionPeriod)-> Res | |||
Types | |||
NetTicktime = integer() >= 1 | |||
TransitionPeriod = integer() >= 0 | |||
Res = | |||
unchanged | change_initiated | | |||
{ongoing_change_to, NewNetTicktime} | |||
NewNetTicktime = integer() >= 1 | |||
将net_ticktime(请参阅 kernel(6))设置为 NetTicktime秒。 TransitionPeriod默认为60。 | |||
一些定义: | |||
Minimum transition traffic interval (MTTI) | |||
minimum(NetTicktime, PreviousNetTicktime)*1000 div 4 milliseconds. | |||
Transition period | |||
调用set_net_ticktime / 2之后,要覆盖TransitionPeriod秒的最少连续MTTI的时间(即(((TransitionPeriod * 1000-1)div MTTI + 1)* MTTI 毫秒)。 | |||
如果 NetTicktime <PreviousNetTicktime,则net_ticktime更改在过渡期结束时进行;否则在开始时。在过渡期间,net_kernel确保至少每MTTI毫秒在所有连接上都有传出流量。 | |||
注意 | |||
所述net_ticktime变化必须在网络中的所有节点(具有相同的上启动NetTicktime任何节点上的任何过渡期结束前); 否则可能会错误地断开连接。 | |||
返回以下之一: | |||
unchanged | |||
net_ticktime已经拥有的价值 NetTicktime和保持不变。 | |||
change_initiated | |||
net_kernel启动了将net_ticktime更改 为NetTicktime 秒。 | |||
{ongoing_change_to,NewNetTicktime} | |||
该请求被忽略,因为 net_kernel忙于将net_ticktime更改为 NewNetTicktime秒。 | |||
## setopts(Node, Options) -> ok | {error, Reason} | ignored | |||
Types | |||
Node = node() | new | |||
Options = [inet:socket_setopt()] | |||
Reason = inet:posix() | noconnection | |||
Set one or more options for distribution sockets。参数节点可以是一个节点名称,也可以是新的原子,以影响所有将来连接的节点的分配套接字。 | |||
如果Node不是连接的节点或new,则返回值与 inet:setopts / 2 或{error,noconnection}相同。 | |||
如果Node是新的,则Options 还将添加到内核配置参数 inet_dist_listen_options 和 inet_dist_connect_options。 | |||
如果本地节点不活动,则返回忽略。 | |||
## start([Name]) -> {ok, pid()} | {error, Reason} | |||
## start([Name, NameType]) -> {ok, pid()} | {error, Reason} | |||
## start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason} | |||
Types | |||
Name = atom() | |||
NameType = shortnames | longnames | |||
Reason = {already_started, pid()} | term() | |||
通过启动net_kernel和其他必要的过程,将非分布式节点转变为分布式节点。 | |||
请注意,该参数是仅包含一个,两个或三个参数的列表。NAMETYPE默认为longnames 和滚动时间至15000。 | |||
## stop() -> ok | {error, Reason} | |||
Types | |||
Reason = not_allowed | not_found | |||
将分布式节点转变为非分布式节点。对于网络中的其他节点,这与发生故障的节点相同。仅当使用start / 1启动网络内核时才可能 ,否则返回{error,not_allowed}。如果本地节点未处于活动状态,则返回 {error,not_found}。 | |||
@ -1,181 +0,0 @@ | |||
erlang 模块的时间函数---------》》》》 | |||
localtime_to_universaltime/1 | |||
如果基础操作系统支持,则将本地日期和时间转换为世界标准时间(UTC). 否则,不进行任何转换,并 返回Localtime | |||
localtime_to_universaltime/2 | |||
将本地日期和时间转换为erlang:localtime_to_universaltime / 1,以协调世界时(UTC),但调用者确定夏令时是否处于活动状态。 | |||
如果IsDst == true,则本地 时间位于夏令时,如果IsDst == false ,则不是。如果IsDst == undefined,则底层操作系统可以猜测, | |||
这与调用 erlang:localtime_to_universaltime(Localtime)相同。 | |||
universaltime_to_localtime/1 | |||
如果基础操作系统支持,则以{{Year,Month,Day},{Hour,Minute,Second}}的形式将世界标准时间(UTC)日期和时间转换为本地日期和时间 。 | |||
否则,不进行任何转换,并 返回Universaltime。例: | |||
time/0 | |||
以{Hour,Minute,Second}的形式返回当前时间。时区和夏令时校正取决于基础操作系统。 | |||
date/0 | |||
返回当前日期为{Year,Month,Day}。时区和夏令时校正取决于基础操作系统。 | |||
localtime/0 | |||
返回当前的本地日期和时间 {{Year,Month,Day},{Hour,Minute,Second}} 时区和夏令时校正取决于基础操作系统。 | |||
universaltime/0 | |||
如果基础操作系统支持,则根据世界标准时间(UTC)以{{Year,Month,Day},{Hour,Minute,Second}}的形式返回当前日期和时间 。 | |||
否则,erlang:universaltime()等效于 erlang:localtime()。例: | |||
posixtime_to_universaltime/1 | |||
posixtime 转为 universaltime | |||
universaltime_to_posixtime/1 | |||
universaltime换为posixtime时间戳 | |||
system_time/0, | |||
以本地时间单位返回当前的 Erlang系统时间。 | |||
system_time/1 | |||
返回当前的 Erlang系统时间, 该时间已转换为作为参数传递的Unit。 | |||
convert_time_unit/3 | |||
转换时间的时间单位的值 FromUnit到相应 ConvertedTime时间单元的值 ToUnit。使用下限功能对结果进行四舍五入。 | |||
警告:在时间单位之间进行转换时,可能会失去准确性和精度。为了最大程度地减少此类损失,请以本地时间单位收集所有数据,然后对最终结果进行转换。 | |||
time_offset/0 | |||
以 本地时间单位返回Erlang单调时间和 Erlang系统时间之间的当前时间偏移 。添加到Erlang单调时间的当前时间偏移会给出相应的Erlang系统时间。 | |||
时间偏移可能会或可能不会在操作期间更改,具体取决于所使用的时间扭曲模式。 | |||
注意: | |||
通过不同的过程,可以在稍有不同的时间点观察到时间偏移量的变化。 | |||
如果运行时系统处于 多时间扭曲模式,则当运行时系统检测到OS系统时间已更改时,时间偏移也会 更改。但是,运行时系统不会立即检测到它。 | |||
检查时间偏移的任务计划至少每分钟执行一次;因此,在正常操作下,一分钟内即可检测到,但是在重负载下可能需要更长的时间。 | |||
time_offset/1 | |||
返回Erlang单调时间和 Erlang系统时间之间的当前时间偏移, 该时间已转换为作为参数传递的Unit。 | |||
timestamp/0 | |||
以{MegaSecs,Secs,MicroSecs}格式返回当前的 Erlang系统时间。此格式与os:timestamp / 0 和不赞成使用的erlang:now / 0相同 。 | |||
存在erlang:timestamp()的原因纯粹是为了简化对采用这种时间戳格式的现有代码的使用。可以使用erlang:system_time / 1以您选择的时间单位 | |||
更有效地检索当前Erlang系统时间 。 | |||
The erlang:timestamp() BIF is equivalent to: | |||
timestamp() -> | |||
ErlangSystemTime = erlang:system_time(microsecond), | |||
MegaSecs = ErlangSystemTime div 1000000000000, | |||
Secs = ErlangSystemTime div 1000000 - MegaSecs*1000000, | |||
MicroSecs = ErlangSystemTime rem 1000000, | |||
{MegaSecs, Secs, MicroSecs}. | |||
calendar 时间模块 -------》》》》》 | |||
模块总结 | |||
本地和世界时间,星期几,日期和时间转换。 | |||
描述 | |||
此模块提供本地和通用时间,星期几以及许多时间转换功能的计算。 | |||
根据当前时区和夏令时进行调整时,时间是本地时间。当它反映的是经度为零的时间时,它是通用的,无需为夏时制进行任何调整。 | |||
世界标准时间(UTC)时间也称为格林威治标准时间(GMT)。 | |||
此模块中的时间函数local_time / 0和 Universal_time / 0都返回日期和时间。这是因为日期和时间的单独功能可能导致日期/时间组合错开24小时。 | |||
如果其中一个功能在午夜之前调用,而另一个功能在午夜之后调用,则会发生这种情况。此问题也适用于Erlang BIF date / 0和time / 0, | |||
如果需要可靠的日期/时间戳,强烈建议不要使用它们。 | |||
所有日期均符合公历。此历法由教皇格雷戈里十三世在1582年引入,从今年开始在所有天主教国家中使用。德国和荷兰的新教部分在1698年采用了它, | |||
英格兰随后在1752年采用了,俄国在1918年(根据格里高利历法,1917年10月的革命发生在11月)。 | |||
此模块中的公历将回溯到0年。对于给定的日期,公历天数是指指定日期之前(包括该日期)的天数。同样,指定日期和时间的公历秒数是直至并包括指定日期和时间的秒数。 | |||
要计算时间间隔之间的差异,请使用计算公历天或秒的功能。如果将纪元指定为本地时间,则必须将其转换为通用时间,以获取各纪元之间经过时间的正确值 | |||
。不建议使用功能time_difference / 2。 | |||
一年中的一周存在不同的定义。该模块包含符合ISO 8601标准的一年中的一周实施。由于指定日期的星期数可以落在上一个,当前或下一年, | |||
因此指定年号和星期数很重要。函数iso_week_number / 0和iso_week_number / 1 返回年份和星期数的元组。 | |||
calendar: | |||
date_to_gregorian_days/1 | |||
date_to_gregorian_days/3 | |||
计算从0年开始到指定日期结束的公历天数。 | |||
datetime_to_gregorian_seconds/1 | |||
计算从年份0开始到指定的日期和时间的公历秒数。 | |||
gregorian_days_to_date/1 | |||
根据指定的公历天数计算日期。 | |||
gregorian_seconds_to_datetime/1 | |||
根据指定的公历秒数计算日期和时间。 | |||
day_of_the_week/1 | |||
day_of_the_week/3 | |||
从指定的Year,Month和 Day计算星期几 。将星期几返回为 1:星期一,2:星期二,依此类推。 | |||
is_leap_year/1 | |||
检查指定的年份是否为闰年。 | |||
iso_week_number/0 | |||
返回表示实际日期的ISO周编号的元组{Year,WeekNum}。要确定实际日期,请使用函数 local_time / 0。 | |||
iso_week_number/1 | |||
返回表示指定日期的ISO周编号的元组{Year,WeekNum}。 | |||
last_day_of_the_month/2 | |||
计算一个月中的天数。 | |||
local_time/0 | |||
等效于 erlang:localtime() | |||
local_time_to_universal_time/1 %% 不推荐使用了 额豁 | |||
从本地时间转换为世界标准时间(UTC)。 DateTime1必须引用1970年1月1日之后的本地日期。 | |||
警告:不推荐使用此功能。请改用 local_time_to_universal_time_dst / 1 ,因为它可以提供更正确和完整的结果。 | |||
尤其是对于不存在的时间段,由于在切换到夏时制时会被跳过,因此此功能仍会返回结果。 | |||
local_time_to_universal_time/2 | |||
local_time_to_universal_time_dst/1 | |||
从本地时间转换为世界标准时间(UTC)。 参数DateTime1必须引用1970年1月1日之后的本地日期。 | |||
返回值是0、1或2个可能的UTC时间的列表: | |||
[] | |||
对于当地时间{Date1,Time1},在切换到夏令时时会跳过该时间段,因此没有相应的UTC,因为当地时间是非法的(从未发生过)。 | |||
[DstDateTimeUTC,DateTimeUTC] | |||
对于从夏令时开始重复的时段中的本地{Date1,Time1},存在两个对应的UTC;一个用于夏令时仍处于活动状态的时段的第一个实例,另一个用于第二个实例。 | |||
[DateTimeUTC] | |||
对于所有其他本地时间,仅存在一个对应的UTC。 | |||
now_to_datetime/1 % = now_to_universal_time/1 | |||
返回从erlang:timestamp / 0的返回值转换的通用协调时间(UTC) 。 | |||
now_to_local_time/1 | |||
返回从erlang:timestamp / 0的返回值转换的本地日期和时间 。 | |||
now_to_universal_time/1 | |||
返回从erlang:timestamp / 0的返回值转换的通用协调时间(UTC) 。 | |||
rfc3339_to_system_time/1 | |||
rfc3339_to_system_time/2 | |||
将RFC 3339时间戳转换为系统时间。RFC 3339描述了RFC 3339时间戳的数据格式 。 | |||
seconds_to_daystime/1 | |||
将指定的秒数转换为天,小时,分钟和秒。时间始终是非负的,但是如果参数Seconds是,则 Days是负的 。 | |||
seconds_to_time/1 | |||
根据指定的秒数计算时间。 秒数必须小于每天的秒数(86400)。 | |||
system_time_to_local_time/2 | |||
将指定的系统时间转换为本地日期和时间。 TODO 优化此函数 | |||
system_time_to_universal_time/2 TODO 优化此函数 | |||
将指定的系统时间转换为通用日期和时间。 | |||
system_time_to_rfc3339/1 | |||
system_time_to_rfc3339/2 | |||
将系统时间转换为RFC 3339时间戳。RFC 3339描述了RFC 3339时间戳的数据格式 。偏移量的数据格式也由RFC 3339描述。 | |||
time_difference/2 %% 改函数过时 不用 | |||
time_to_seconds/1 | |||
返回自午夜到指定时间的秒数。 | |||
universal_time/0 | |||
等效于erlang:universaltime(). | |||
universal_time_to_local_time/1 | |||
erlang:universaltime_to_localtime(DateTime). | |||
valid_date/1 | |||
valid_date/3 | |||
此功能检查日期是否有效。 |