并发编程

Posted 夏天的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程相关的知识,希望对你有一定的参考价值。

% (1).编写一个start(AnAtom, Fun)函数来把spawn(Fun)注册为AnAtom。
% 确保当两个并行的进程同时执行start/2时你的程序也能正确工作。
% 在这种情况下,必须保证其中一个进程会成功执行而另一个会失败。


-module(execstart).
-export([create/2]).

start(AnAtom, Fun) ->
    case whereis(AnAtom) of
        Pid -> io:format("this AnAtom have registered~n");
        undefined -> register(AnAtom, spawn(Fun)),
                     io:format("this is process name:~p~n", [AnAtom])
    end.

create(AnAtom, Fun) ->
    start(AnAtom, Fun),
    start(AnAtom, Fun).

这段程序结果很奇怪,结果二者都不能创建。

case的两个子句交换下位置就好。

-module(execstart).
-export([create/2]).

start(AnAtom, Fun) ->
    case whereis(AnAtom) of
        undefined -> register(AnAtom, spawn(Fun)),
                 io:format("this is process name:~p~n", [AnAtom]);
        Pid -> io:format("this AnAtom have registered~n")
    end.

create(AnAtom, Fun) ->
    start(AnAtom, Fun),
    start(AnAtom, Fun).

因为Pid匹配的范围大,它可以匹配undefined,所以第一段代码中undefined永远不会被匹配。我们在写程序的时候需要注意,范围小的放在前面,范围大的放在后面,避免大范围包含小范围这种情况。

第二段结果


% (2).用12.3节里的程序在你的机器上测量一下进程分裂所需要的时间。
% 在一张进程数量对进程创建时间的图上进行标绘。
% 你能从中得到什么结论。

-module(processes).
-export([max/1]).

max(N) ->
    Max = erlang:system_info(process_limit),
    io:format("Maximum allowed processes:~p~n", [Max]),
    % statistics 返回跟指定类型 Type 相关的系统信息
    % runtime 是cpu耗时,wall_clock 是代码运行时间
    statistics(runtime),
    statistics(wall_clock),
    L = for(1, N, fun() -> spawn(fun() -> wait() end) end),
    _, Time1 = statistics(runtime),
    _, Time2 = statistics(wall_clock),
    lists:foreach(fun(Pid) -> Pid ! die end, L),
    U1 = Time1 * 1000 / N,
    U2 = Time2 * 1000 / N,
    io:format("cpu:~p, time:~p~n", [U1, U2]).

wait() ->
    receive 
        die -> void
    end.


for(N, N, F) -> [F()];
for(I, N, F) -> [F() | for(I+1, N, F)].

这里就不做表格了…

从上图能看出,无论是创建100还是100000个进程,cpu和程序耗时都是非常短的,而且进程数量和实现不是线性关系。


% (3).编写一个环形计时测试。创建一个由N个进程组成的环。
% 把一个消息沿着环发送M次,这样总共发送的消息是M*N。
% 记录不同的N和M值所花费的时间
% 用你熟悉的其他编程语言编写一个类似的程序,然后比较一下结果。
% 写一篇博客,把结果在网上发表出来。

-module(sendMessage).
-export([start/2]).


% N 个进程, M 次
start(N, M) ->
    sendstart(M, createProcess(N)).

% 创建 N 个进程
createProcess(N) ->
    L = for(1, N, fun() -> spawn(fun() -> loop() end) end),
    L.

% 起始
sendstart(M, L) ->
    % 给第一个进程发送请求,开始绕环发送消息
    Pid = getPid(1, L, M),
    Pid ! 1, L, M, "hello world".

% 进程执行的函数
loop() ->
    receive 
        % 1, [1,2,3,4,5], "hello world"
        I, L, M, Message -> 
            io:format("Pid:~p Recv Message:~p~n", [(I rem lists_size(L))+1,Message]),
            % 发送给下一个进程
            case getPid(I+1, L, M) of
                none -> io:format("have send...~n");
                Pid  -> Pid ! I+1, L, M, Message,
                        loop()   
            end
    end.

% 获得进程的 Pid。
getPid(I, L, M) ->
    io:format("...........I:~p~n", [I]),
    case lists_size(L) of
        Size when I =< Size ->
            lists:nth(I, L);
        % 注意余数为 0
        Size when I > Size, I rem Size =:= 0 ->
            lists:nth(Size, L);
        Size when I > Size, I =< M ->
            lists:nth((I rem Size), L);
        Size when I > M ->
            none
    end.

for(Max, Max, F) -> [F()];
for(I, Max, F)   -> [F() | for(I+1, Max, F)].


% 计算lists大小函数
lists_size(L) ->
   lists_size(L, 0).
lists_size([_|T], Size) ->
    lists_size(T, Size+1);
lists_size([], Size) ->
    Size.

至于题目要求性能对比,其实结果很明显,erlang的进程不是操作系统范畴的,不需要切换,不需要保存上下文,而c/c++包括Java,它们的进程都非常重,上下文切换非常耗时,Linux下可以看看task_struct这个结构体,保存进程的信息,而erlang进程属于虚拟机,非常轻,不共享内存,所以速度肯定非常快。
等闲了再写篇测试博客^_^

以上是关于并发编程的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 中测量 DispatchQueue 并发异步中的代码块执行时间?

并发读取会导致开销吗?

函数式编程第一弹

读编程与类型系统笔记11_高级类型及其他

函数式编程理解 - currycompose

《前端内参》,有关于JavaScript、编程范式、设计模式、软件开发的艺术等大前端范畴内的知识分享,旨在帮助前端工程师们夯实技术基础以通过一线互联网企业技术面试。