Tải bản đầy đủ - 0 (trang)
VI. LẬP TRÌNH TƯƠNG TRANH TRONG ERLANG

VI. LẬP TRÌNH TƯƠNG TRANH TRONG ERLANG

Tải bản đầy đủ - 0trang

Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



thực hiện song song và không cần các cơ chế để kiểm tra các side effect này khi thực hiện

song song.

Khi xảy ra hiện tượng các process bị crash, cách ứng xử của các process cũng sẽ

giống với các cá nhân thực hiện các công việc đó là các process trước khi exit sẽ thực

hiện thơng bác đến các process được liên kết với nó thông tin về việc exit cùng với

nguyên nhân của việc exit này. Từ đó các process tương ứng này sẽ xác định việc phải

làm khi process khác bị crash.

Các process trong Erlang nhỏ và rất dễ để gọi, hủy bỏ cũng như liên lạc giữa chúng

cũng khá đơn giản vì vậy khi cần tăng tốc độ hoặc cần thực hiện cơng việc mới ta có thể

gọi thêm process mà khơng lo các yêu cầu về tài nguyên bộ nhớ cũng như việc quản lý

các process.



6.2.Process trong Erlang

Để thực hiện các thao tác làm việc cũng như thực thi các ứng dụng thơng qua các

process ta tìm hiểu về cách gọi cũng như các thao tác thực hiện với process. Có ba thao

tác cơ sở với process đó là spawn, send, receive :

• Spawn là việc thực hiện tạo lập một process mới.

• Send là việc gửi một message đến một process.

• Receive là các đáp ứng của một process khi các message được chuyển đến.

Để thực hiện quá trình Spawn ta chỉ cần thực hiện lời gọi hàm sau :

Pid = spawn(Fun)

Khi đó hàm spawn sẽ tạo ra một process và trả về kết quả là con trỏ đến process

thực hiện hàm Fun. Các gọi hàm đầy đủ như sau cùng với các yêu cầu về các tham số đầu

vào :

spawn(Module, Name, Args) -> pid()

Module = Name = atom()

Args = [Arg1,...,ArgN]

ArgI = term()



Ngồi ra còn rất nhiều các hàm BIF spawn khác với số lượng các tham số khác

nhau để thực hiện tạo ra một process.

Với quá trình Send ta chỉ cần thực hiện phép toán rất đơn giản với toán tử “!”.

Muốn gửi một Message đến một process (Pid) ta thực thiện phép tốn:

Pid ! Message.



Ngun lý các Ngơn ngữ lập trình



57



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



Để gửi đến nhiều process khác nhau với cùng một Message ta có một cách viết gọn



Pid1 ! Pid2 ! ... ! M



Khi các tin nhắn đến các process ta thực hiện Receive các tin nhắn thông qua

expression sau:

receive

Pattern1 [when GuardSeq1] ->

Body1;

...;

PatternN [when GuardSeqN] ->

BodyN

end



Các tin nhắn sẽ lần lượt được so với các mẫu Pattern1, … PatternN nếu phù hợp với

mẫu nào sẽ kiểm tra GuardSeq tương ứng nếu phù hợp sẽ thực hiện Body tương ứng.

Bên cạnh mẫu Receive như trên người ta còn có mẫu receive … after:

Trong đó ExprT là một số nguyên (integer), giá trị lớn nhất của nó được phép là

16#FFFFFFFF

Có một cách rất hay để tạo nên mức ưu tiên với các message được gửi đến đó là tạo

các khối receive lồng trong after 0 như sau:

receive

Pattern1 [when GuardSeq1]

Body1;

...;

PatternN [when GuardSeqN]

BodyN

after

0 ->

receive

PatternExt1 [when

Body1;

...;

PatternExtN [when

BodyN

end



->

->



GuardSeq1] ->

GuardSeqN] ->



Tuy nhiên không nên quá lạm dụng cách làm này với số lượng message lớn.

Ngoài các phép cơ bản trên ta còn có cách phép tốn khác khi thực hiện với các

process:

Ngun lý các Ngơn ngữ lập trình



58



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



• Đăng ký - register: Bên cạnh việc đánh địa chỉ cho các process thông qua các Pid,

ta cũng được cung cấp những BIF riêng dành cho việc đăng ký một tên cho một

process quá trình này được gọi là register. Mỗi tên được đăng ký cho một process

phải là một atom, và khi process kết thúc tên này sẽ tự động được hủy đăng ký

unregister. Dưới đây là bảng cho thấy các hàm thực hiện với phép toán:

Các phép tốn

register(Name, Pid)

register()

whereis(Name)



Giải thích

Đăng ký tên Name cho Pid

Đưa ra một List các Pid đã được đăng ký

Trá lại kết quả là một Pid nếu Name đã

được đăng ký cho Pid, trả lại kết quả là

undefined nếu như chưa có Pid nào đăng

ký với tên là Name.

unregister(Name)

Hủy đăng ký của Name

• Từ điển của process: Mỗi process có một từ điển riêng từ điển này cho phép ta có

thể ghi giá trị vào phục vụ khá hiệu quả trong việc lập trình các hàm sau dùng để

thực hiện các quá trình cập nhật trên từ điển này:

put(Key, Value)

get(Key)

get()

get_keys(Value)

erase(Key)

erase()



6.3.Exception Handling với các Process

Với các exception handling thơng thường đã được trình bày ở phần trước với cách

viết ứng dụng một cách tuần tự. Trong một process đơn thì việc xử lý các ngoại lệ cũng

tương tự như vậy. Nhưng với một hệ thống các process thì việc quản lý với các ngoại lệ

được một cách đặc biệt.

Các process thông thường khi gặp lỗi sẽ tự động kết thúc nếu khơng có một cách

quản lý hiệu quá các ứng dụng sẽ không đạt được kết quả cuối cùng và rất khó để biết

được lý do process bị gặp lỗi. Để xử lý được điều này Erlang xử lý giống như đã nói ở

trên, khi một process kết thúc do gặp lỗi (die) nó sẽ gửi những thơng tin này đến các

process có quan hệ với nó để các process này có thể có được những thơng tin về lý do của

process đã bị kết thúc do gặp lỗi.

Để tạo kết nối giữa hai process Erlang cung cấp phép toán link giữa các process.

Muốn tạo kết nối process A với process B thì trong phần thân của A cần có lời gọi

link(PidB) hoặc ngược lại trong phần thân của B có lời gọi link(PidA). Đây là một kết nối

2 chiều tức nếu A được link đến B thì B cũng đã được link đến A theo lời gọi.

Nguyên lý các Ngôn ngữ lập trình



59



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



Tập hợp các process đã link đến process B được gọi là link set của process B.

Nếu vì một lý do nào đó mà process B bị kết thúc do lỗi nó sẽ gửi một thông điệp

thông báo đến tất cả các process trong tập link set của mình trước khi thật sự kết thúc.

Khi một thông điệp thông báo kết thúc do lỗi được gửi đến một process thơng thường thì

process này cũng tự động báo lỗi và kết thúc đến cho các process nằm trong tập link set

của mình. Như vậy sẽ khó để lưu lại thông tin lỗi của các process để làm được điều này ta

thực hiện theo hai cách sau:

a. Biến một process thông thường thành system process, đây là một process đặc biệt

khi các tín hiệu thơng báo kết thúc do lỗi được truyền đến nó sẽ được đưa vào mail

box của process này với định dạng như sau: {'EXIT', Pid, Why} và

process này sẽ thực hiện xử lý message này như các message thông thường khác.

Để thực hiện được điều này ta thực hiện lời gọi hàm BIF, ví dụ muốn biến process

PidA thành system process thì trong thân của process PidA cần phải có lời gọi

process_flag(trap_exit, true).

b. Tạo một monitor, đây là một trường hợp kết nối đặc biết khác với link. Để tạo

được loại kết nối này ta thực hiện lời gọi hàm BIF, ví dụ muốn tạo monitor từ Pid1

đến Pid2 thì trong thân của Pid1 phải có lời gọi erlang:monitor(process,

Pid2) hàm này sẽ trả về một reference.

Khi đó nếu Pid2 bị kết thúc do lỗi nguyên nhân là Reason thì một message ‘DOWN’ sẽ

được đưa đến mail box của Pid1 với cấu trúc như sau: {'DOWN', Ref, process, Pid2, Reason}

Monitor là một quan hệ một chiều khác với link. Để hủy một monitor đến một Pid ta thực

hiện lời gọi hàm erlang:demonitor(Ref).



Như vậy với mỗi process bất kỳ ta có thể có một bảng như sau:

Trap_exit Exit Signal



Hành động

Kết thúc và gửi thông điệp thoát do lỗi cùng với nguyên

nhân cho các process trong link set của mình

Khơng kết thúc, nhận được tin nhắn thông báo vào mail box

và xử lý thông tin này như đã được lập trình



true



kill



true



X



false



normal



Khơng kết thúc tiếp tục chạy



false



kill



Kết thúc và gửi thơng điệp thốt do lỗi cùng với nguyên

nhân cho các process trong link set của mình



Nguyên lý các Ngơn ngữ lập trình



60



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



false



X



Khơng kết thúc, nhận được tin nhắn thông báo vào mail box

và xử lý thông tin này như đã được lập trình



Trong đó:

• Trap_exit: là tham số vào cho lời gọi hàm process_flag, nếu là true thì process này

là process system, nếu là false thì là process thơng thường.

• Exit Signal: đây là các tín hiệu thơng báo kết thúc từ process khác được đưa đến

process này

o Kill: là exit signal khơng thể trap được vì vậy khi thơng điệp này được gửi

đến thì process dù có là system process hay không cũng đều phải kết thúc.

o X: là thông điệp kết thúc do lỗi từ một process khác gửi đến với lý do là X.

o Normal: là tín hiệu kết thúc do process khác gửi đến mà process này kết

thúc do hồn thành cơng việc của mình chứ khơng phải do crash.

Từ đây mỗi khi một process được khởi tạo có 3 cách như sau:

1. Tạo một process nhưng khơng quan tâm đến việc nó có bị crash hay khơng. Sử

dụng lời gọi spawn thông thường:

Pid = spawn(fun() -> ... end)



2. Tạo một process mà cần theo dõi xem nó có bị crash hay khơng. Ta sẽ sử dụng

một lời gọi spawn_link như sau:

Pid = spawn_link(fun() -> ... end)



3. Tạo một process để theo dõi các process khác xem chúng có bị crash hay khơng.

Ta làm như sau:

...

process_flag(trap_exit, true),

Pid = spawn_link(fun() -> ... end),

...

loop(...).

loop(State) ->

receive

{'EXIT', SomePid, Reason} ->

%% do something with the error

loop(State1);

...

End



Để tạo hiệu quả trong quá trình kiểm sốt lỗi các process người ta tạo ra các nhóm

các process cùng chức năng tính tốn tạo thành một linkset như vậy khi có một process bị

crash thì tất cả các process đều kết thúc theo. Như hình dưới



Nguyên lý các Ngơn ngữ lập trình



61



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



6.4. Giao tiếp với các phần mềm khác (Interfacing Technique)

Giả sử chúng ta muốn giao diện Erlang có thể kết nối đến một chương trình được

viết bằng C hoặc Python hay chạy một kịch bản từ Erlang. Để làm được điều này, chúng

ta chạy chương trình từ bên ngồi trong process của hệ điều hành riêng bên ngồi chương

trình Erlang lúc runtime, và liên kết process này thông qua kênh giao tiếp hướng byte –

byte-oriented communication channel. Sự liên kết của phía Erlang được điều khiển thơng

qua Erlang port – cổng giao tiếp Erlang. Process mà tạo ra một cổng được gọi là process

được kết nối – connected process cho cổng đó. Q trình kết nối này có ý nghĩa đặc biệt:

tất cả các thơng điệp tới chương trình bên ngồi phải được đánh dấu với một PID của

process kết nối, và tất cả thơng điệp từ chương trình bên ngồi được gửi tới process kết

nối này.



Hình 4. Giao tiếp giữa chương trình viết bằng Erlang và ngơn ngữ khác.



Ngun lý các Ngơn ngữ lập trình



62



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang



Đối với người lập trình, port được đối xử như một Erlang process. Ta có thể gửi

thơng điệp tới nó, đăng ký nó và nhiều hơn nữa. Nếu chương trình bên ngoại bị treo, thì

dấu hiệu exit – kết thúc sẽ được gửi tới process kết nối, và nếu process kết nối bị ngắt, thì

chương trình bên ngồi sẽ kết thúc ngay.

Rất nhiều chương trình cho phép code trong các ngơn ngữ khác được kết nối tới ứng

dụng có thể được thực hiện được – application executable. Trong Erlang, chúng ta khơng

cho phép điều này vì tính an tồn của nó. Nếu chúng ta kết nối chương trình bến ngồi

vào Erlang exe, thì dễ dàng có sự can thiệp của chương trình ngồi gây lỗi cho hệ thống

Erlang. Vì vậy, tất cả các ngơn ngữ bên ngồi phải được chạy ngồi hệ thống Erlang

trong một hệ thống hoạt động bên ngoài q trình. Hệ thống Erlang và process bên ngồi

kết nối với nhau thông qua luồng byte.



6.4.1.Ports:

Để giao tiếp với các phần mềm khác, hoặc giao tiếp với các kịch bản Erlang khác, ta

phải giao tiếp qua Ports. Cú pháp tạo ra một port như sau:

Port = open_port(PortNam, PortSettings)



Chúng ta sẽ nhận được một port. Thơng điệp có thể được gửi tới một port như sau:

Port ! {PidC, {command, Data}}



Gửi dữ liệu (danh sách vào ra) tới cổng này

Port ! {PidC, {connect, Pid1}



Thay đổi PID của process kết nối từ PidC tới Pid1.

Port ! {PidC, close}



Đóng port.

Process kết nối có thể nhận được thơng điệp từ chương trình ngồi như

sau:

receive

{Port, {data, Data}} ->

... Data comes from the external process ...



Dưới đây, chúng ta sẽ tương tác Erlang với một chương tình C hết sức đơn giản.

Chú ý ràng: Ví dụ sau là cố ý đơn giản để làm nổi bật các cơ chế port, giao thức protocol. Việc mã hóa và giải mã cấu trúc dữ liệu phức tạp là một vấn đề khó, và chúng ta

sẽ khơng đề cập đến ở đây.



6.4.2.Tương tác với chương trình C bên ngồi:

example1.c



Ngun lý các Ngơn ngữ lập trình



63



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang

int twice(int x) {

return 2 * x;

}

int sum (int x, int y) {

return x + y;

}



Chúng ta có thể gọi chương trình này như sau:

X1 = examples1:twice(23),

Y1 = examples2:sum(45, 32),



Giao diện của chúng ta cần một chương trình chính mà giả mã dữ liệu được gửi từ

chương trình Erlang. Trong ví dụ của chúng ta, trước tiên chúng ta xác định một giao

thức giữa cổng và các chương trình C bên ngoài. Chúng ta sẽ sử dụng một cực kỳ đơn

giản giao thức và sau đó hiển thị như thế nào để thực hiện điều này trong Erlang và C.

Giao thức được định nghĩa như sau:

• Tất cả các gói bắt đầu với độ dài 2 byte code (Len) sinh ra bởi Chiều dài byte

của dữ liệu.

• Để gọi twice(N), Erlang phải mã hóa cách gọi hàm này sử dụng một vài bước

biễn đổi. Chúng được mã hó như là 2-byte chuỗi [1, N]; trong đó 1 có nghĩa

là gọi hàm twice, và N là (1-byte) một đối số.

• Đề gọi sum(N, M), ta sẽ mã hóa yêu cầu này theo thức tự byte sau [2, N, M].

• Giá trị trả về là kiều single byte long.

Cả chương trình C và Erlang phải được sử dụng theo giao thức này. Ví dụ sau sẽ

giúp ta hiểu phần nào cách để tính sum(45, 32):”

• Port gửi thơng điệp byte tuần tự 0, 3, 2, 45, 32 tới chương trình bên ngồi.

• 2 byte đầu tiên, 0, 3 là độ dài của gói (3), 2 có nghĩa là gọi hàm thứ sum, 45,

và 32 là các tham số.

• Chương trình bên ngồi đọc vào 5 byte từ dạng đầu vào chuẩn, triệu gọi sum,

và viết byte tuàn từ 0, 2, 77 tới dạng đầu ra chuẩn

• 2 byte đầu tiên là chiều dài lời gọi, giá trị tiếp theo là kết quả trả về 77

(khoảng 1-byte long).

Bây giờ chúng ta sẽ viết chương trình trên cả 2 phía. Bắt đầu từ chương trình C:

#include

typedef unsigned char byte;



Nguyên lý các Ngôn ngữ lập trình



64



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang

int read_cmd(byte *buff);

int write_cmd(byte *buff, int len);

int main() {

int fn, arg1, arg2, result;

byte buff[100];

while (read_cmd(buff) > 0) {

fn = buff[0];

if (fn == 1) {

arg1 = buff[1];

result = twice(arg1);

} else if (fn == 2) {

arg1 = buff[1];

arg2 = buff[2];

/*

debug -- you can print to stderr to debug

fprintf(stderr,"calling sum %i %i\n",arg1,arg2);

*/



}



result = sum(arg1, arg2);

}

buff[0] = result;

write_cmd(buff, 1);



}



Đoạn code này chay một vòng lặp khơng xác định để nhận lệnh từ các đầu vào

chuẩn gọi các ứng dụng thông thường và ghi kết quá lên các đầu ra chuẩn.

Nếu ta muốn debug – gỡ lỗi chương trình này chúng ta có thể viết lên stderr. Một ví

dụ về mệnh đề debug:

--Erl_comm.c

/* erl_comm.c */

#include

typedef unsigned char byte;

int

int

int

int



read_cmd(byte *buf);

write_cmd(byte *buf, int len);

read_exact(byte *buf, int len);

write_exact(byte *buf, int len);



int read_cmd(byte *buf)

{

int len;

if (read_exact(buf, 2) != 2)

return(-1);

len = (buf[0] << 8) | buf[1];



Ngun lý các Ngơn ngữ lập trình



65



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang

return read_exact(buf, len);

}

int write_cmd(byte *buf, int len)

{

byte li;

li = (len >> 8) & 0xff;

write_exact(&li, 1);

li = len & 0xff;

write_exact(&li, 1);

}



return write_exact(buf, len);



int read_exact(byte *buf, int len)

{

int i, got=0;

do {

if ((i = read(0, buf+got, len-got)) <= 0)

return(i);

got += i;

} while (got
return(len);

}

int write_exact(byte *buf, int len)

{

int i, wrote = 0;

do {



if ((i = write(1, buf+wrote, len-wrote)) <= 0)

return (i);

wrote += i;

} while (wrote
}



return (len);



Mã này là chuyên để xử lý các gói dữ liệu với chiều dài 2-bytetiêu đề, vì vậy nó phù

hợp với (packet, 2) lựa chọn cho port điều khiển chương trình.

Chương trình erlang

Example1.erl

%% --%% Excerpted from "Programming Erlang",

%% published by The Pragmatic Bookshelf.



Nguyên lý các Ngôn ngữ lập trình



66



Lập trình song song và lập trình tương tranh, Tìm hiểu về ngơn ngữ lập trình

Erlang

%%

Copyrights apply to this code. It may not be used to create

training material,

%% courses, books, articles, and the like. Contact us if you are

in doubt.

%% We make no guarantees that this code is fit for any purpose.

%%

Visit http://www.pragmaticprogrammer.com/titles/jaerlang for

more book information.

%%---module(example1).

-export([start/0, stop/0]).

-export([twice/1, sum/2]).

start() ->

spawn(fun() ->

register(example1, self()),

process_flag(trap_exit, true),

Port = open_port({spawn, "./example1"}, [{packet, 2}]),

loop(Port)

end).

stop() ->

example1 ! stop.

twice(X) -> call_port({twice, X}).

sum(X,Y) -> call_port({sum, X, Y}).

call_port(Msg) ->

example1 ! {call, self(), Msg},

receive

{example1, Result} ->

Result

end.

loop(Port) ->

receive

{call, Caller, Msg} ->

Port ! {self(), {command, encode(Msg)}},

receive

{Port, {data, Data}} ->

Caller ! {example1, decode(Data)}

end,

loop(Port);

stop ->

Port ! {self(), close},

receive

{Port, closed} ->

exit(normal)

end;

{'EXIT', Port, Reason} ->

exit({port_terminated,Reason})

end.



Nguyên lý các Ngơn ngữ lập trình



67



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

VI. LẬP TRÌNH TƯƠNG TRANH TRONG ERLANG

Tải bản đầy đủ ngay(0 tr)

×