Đây là lệnh perlthrtut có thể chạy trong nhà cung cấp dịch vụ lưu trữ miễn phí OnWorks bằng cách sử dụng một trong nhiều máy trạm trực tuyến miễn phí của chúng tôi như Ubuntu Online, Fedora Online, trình giả lập trực tuyến Windows hoặc trình mô phỏng trực tuyến MAC OS
CHƯƠNG TRÌNH:
TÊN
perlthrtut - Hướng dẫn về chuỗi trong Perl
MÔ TẢ
Hướng dẫn này mô tả việc sử dụng các chuỗi thông dịch Perl (đôi khi được gọi là
nó). Trong mô hình này, mỗi luồng chạy trong trình thông dịch Perl của riêng nó và bất kỳ dữ liệu nào
chia sẻ giữa các chủ đề phải rõ ràng. Giao diện cấp người dùng cho nó sử dụng
lớp chủ đề.
LƯU Ý: Có một hương vị phân luồng Perl cũ hơn được gọi là mô hình 5.005 sử dụng
lớp chủ đề. Mô hình cũ này được xác định là có vấn đề, không được dùng nữa và đã bị xóa
cho bản phát hành 5.10. Chúng tôi rất khuyến khích bạn di chuyển bất kỳ mã chuỗi 5.005 hiện có nào
sang mô hình mới càng sớm càng tốt.
Bạn có thể xem bạn có (hoặc không) hương vị luồng nào bằng cách chạy "perl -V" và tìm kiếm
tại phần "Nền tảng". Nếu bạn có "useithreads = xác định", bạn có ithreads, nếu bạn
có "use5005threads = xác định" bạn có 5.005 chủ đề. Nếu bạn không có, bạn không có
bất kỳ hỗ trợ luồng nào được tích hợp sẵn. Nếu bạn có cả hai, bạn đang gặp rắc rối.
Các luồng và luồng :: mô-đun được chia sẻ được bao gồm trong phân phối Perl cốt lõi.
Ngoài ra, chúng được duy trì như một mô-đun riêng biệt trên CPAN, vì vậy bạn có thể kiểm tra ở đó
cho bất kỳ cập nhật.
Điều gì Is A Sợi chỉ Dù sao?
Một luồng là một luồng điều khiển thông qua một chương trình với một điểm thực thi duy nhất.
Nghe có vẻ giống một quá trình khủng khiếp, phải không? Vâng, nó nên. Chủ đề là một trong những
các phần của một quy trình. Mọi quy trình đều có ít nhất một chuỗi và cho đến nay, mọi
tiến trình chạy Perl chỉ có một luồng. Tuy nhiên, với 5.8, bạn có thể tạo thêm các luồng.
Chúng tôi sẽ chỉ cho bạn cách thức, khi nào và tại sao.
Có ren chương trình mô hình
Có ba cách cơ bản để bạn có thể cấu trúc một chương trình phân luồng. Bạn mô hình nào
lựa chọn phụ thuộc vào những gì bạn cần chương trình của bạn để làm. Đối với nhiều luồng không tầm thường
chương trình, bạn sẽ cần chọn các mô hình khác nhau cho các phần khác nhau của chương trình của bạn.
Ông chủ / Công nhân
Mô hình ông chủ / công nhân thường có một ông chủ chủ đề và một hoặc nhiều công nhân chủ đề. Các
chuỗi ông chủ tập hợp hoặc tạo các nhiệm vụ cần phải hoàn thành, sau đó chia nhỏ các nhiệm vụ đó ra
đến luồng công nhân thích hợp.
Mô hình này phổ biến trong GUI và các chương trình máy chủ, nơi một luồng chính chờ một số sự kiện
và sau đó chuyển sự kiện đó đến các luồng công nhân thích hợp để xử lý. Một khi
sự kiện đã được chuyển qua, chuỗi ông chủ quay trở lại để chờ sự kiện khác.
Chủ đề ông chủ hoạt động tương đối ít. Mặc dù các nhiệm vụ không nhất thiết phải được thực hiện
nhanh hơn bất kỳ phương pháp nào khác, nó có xu hướng có thời gian phản hồi của người dùng tốt nhất.
Công việc Crew
Trong mô hình nhóm làm việc, một số luồng được tạo về cơ bản thực hiện cùng một việc
các phần dữ liệu khác nhau. Nó phản ánh chặt chẽ quá trình xử lý song song cổ điển và vectơ
bộ vi xử lý, trong đó một mảng lớn các bộ xử lý làm cùng một việc chính xác với nhiều phần của
dữ liệu.
Mô hình này đặc biệt hữu ích nếu hệ thống đang chạy chương trình sẽ phân phối
nhiều luồng trên các bộ xử lý khác nhau. Nó cũng có thể hữu ích trong việc dò tia hoặc
công cụ kết xuất, nơi các luồng riêng lẻ có thể chuyển các kết quả tạm thời để cung cấp
phản hồi trực quan của người dùng.
Pipeline
Mô hình đường ống chia nhiệm vụ thành một loạt các bước và chuyển kết quả của một
chuyển sang xử lý luồng tiếp theo. Mỗi chủ đề làm một việc với mỗi phần của
dữ liệu và chuyển kết quả cho luồng tiếp theo trong dòng.
Mô hình này có ý nghĩa nhất nếu bạn có nhiều bộ xử lý nên hai hoặc nhiều luồng
sẽ được thực thi song song, mặc dù nó cũng có thể có ý nghĩa trong các ngữ cảnh khác.
Nó có xu hướng giữ cho các nhiệm vụ riêng lẻ nhỏ và đơn giản, cũng như cho phép một số phần của
đường ống dẫn để chặn (ví dụ: trên I / O hoặc các cuộc gọi hệ thống) trong khi các bộ phận khác vẫn tiếp tục.
Nếu bạn đang chạy các phần khác nhau của đường dẫn trên các bộ xử lý khác nhau, bạn cũng có thể
tận dụng các bộ nhớ đệm trên mỗi bộ xử lý.
Mô hình này cũng hữu ích cho một hình thức lập trình đệ quy, thay vì có
chương trình con gọi chính nó, thay vào đó nó tạo ra một luồng khác. Bộ tạo Prime và Fibonacci
cả hai ánh xạ tốt đến dạng này của mô hình đường ống. (Một phiên bản của bộ tạo số nguyên tố
sẽ được trình bày ở phần sau.)
Điều gì loại of chủ đề đang Perl chủ đề?
Nếu bạn có kinh nghiệm với các triển khai chuỗi khác, bạn có thể thấy rằng những điều
không hoàn toàn như những gì bạn mong đợi. Điều rất quan trọng cần nhớ khi xử lý Perl
chủ đề đó Perl Chủ đề Có Không X Chủ đề cho tất cả các giá trị của X. Chúng không phải là POSIX
chủ đề, hoặc DecThreads, hoặc chủ đề Xanh của Java, hoặc chủ đề Win32. Có
các điểm tương đồng và các khái niệm rộng đều giống nhau, nhưng nếu bạn bắt đầu tìm kiếm
chi tiết triển khai bạn sẽ thất vọng hoặc bối rối. Có thể là cả hai.
Điều này không có nghĩa là các luồng Perl hoàn toàn khác với mọi thứ đã từng
đến trước. Không phải vậy. Mô hình luồng của Perl mắc nợ rất nhiều so với các mô hình luồng khác,
đặc biệt là POSIX. Tuy nhiên, cũng giống như Perl không phải là C, các luồng Perl không phải là các luồng POSIX. Cho nên
nếu bạn thấy mình đang tìm kiếm mutexes hoặc các ưu tiên của chuỗi, thì đã đến lúc lùi lại
và suy nghĩ về những gì bạn muốn làm và cách Perl có thể làm điều đó.
Tuy nhiên, điều quan trọng cần nhớ là các luồng Perl không thể thực hiện một cách kỳ diệu trừ khi
luồng của hệ điều hành của bạn cho phép nó. Vì vậy, nếu hệ thống của bạn chặn toàn bộ quá trình trên
"sleep ()", Perl thường cũng vậy.
Perl Chủ đề Có Khác nhau.
An toàn chủ đề Modules
Việc bổ sung các luồng đã thay đổi đáng kể nội bộ của Perl. Có những hàm ý
dành cho những người viết mô-đun bằng mã XS hoặc thư viện bên ngoài. Tuy nhiên, vì dữ liệu Perl
không được chia sẻ giữa các luồng theo mặc định, các mô-đun Perl có khả năng cao là luồng-
an toàn hoặc có thể được tạo ren an toàn một cách dễ dàng. Các mô-đun không được gắn thẻ là an toàn chuỗi sẽ
được kiểm tra hoặc đánh giá mã trước khi được sử dụng trong mã sản xuất.
Không phải tất cả các mô-đun mà bạn có thể sử dụng đều an toàn cho chuỗi và bạn nên luôn cho rằng một mô-đun
là không an toàn trừ khi tài liệu hướng dẫn khác. Điều này bao gồm các mô-đun được
được phân phối như một phần của lõi. Chủ đề là một tính năng tương đối mới và thậm chí một số
các mô-đun tiêu chuẩn không an toàn cho luồng.
Ngay cả khi một mô-đun an toàn theo luồng, điều đó không có nghĩa là mô-đun đó được tối ưu hóa để hoạt động tốt
với chủ đề. Một mô-đun có thể được viết lại để sử dụng các tính năng mới trong luồng
Perl để tăng hiệu suất trong môi trường luồng.
Nếu bạn đang sử dụng một mô-đun không an toàn cho luồng vì lý do nào đó, bạn có thể tự bảo vệ mình
bằng cách sử dụng nó từ một và chỉ một chủ đề. Nếu bạn cần nhiều chuỗi để truy cập
một mô-đun như vậy, bạn có thể sử dụng semaphores và rất nhiều kỷ luật lập trình để kiểm soát quyền truy cập
với nó. Semaphores được đề cập trong "Semaphores cơ bản".
Xem thêm "An toàn chuỗi của Thư viện Hệ thống".
Sợi chỉ Khái niệm cơ bản
Mô-đun luồng cung cấp các chức năng cơ bản bạn cần để viết các chương trình luồng. Trong
các phần sau, chúng tôi sẽ trình bày những kiến thức cơ bản, cho bạn thấy những gì bạn cần làm để tạo
một chương trình phân luồng. Sau đó, chúng ta sẽ xem xét một số tính năng của mô-đun luồng
giúp lập trình theo luồng dễ dàng hơn.
Cơ bản Sợi chỉ Hỗ Trợ
Hỗ trợ luồng là một tùy chọn thời gian biên dịch Perl. Đó là thứ được bật hoặc tắt khi
Perl được xây dựng tại trang web của bạn, thay vì khi các chương trình của bạn được biên dịch. Nếu Perl của bạn
không được biên dịch với hỗ trợ luồng được bật, khi đó mọi nỗ lực sử dụng luồng sẽ không thành công.
Các chương trình của bạn có thể sử dụng mô-đun Cấu hình để kiểm tra xem các luồng có được kích hoạt hay không. Nếu là của bạn
chương trình không thể chạy mà không có chúng, bạn có thể nói điều gì đó như:
sử dụng Config;
$ Config {useithreads} hoặc
die ('Biên dịch lại Perl với các luồng để chạy chương trình này.');
Một chương trình có thể có luồng sử dụng mô-đun có thể có luồng có thể có mã như sau:
sử dụng Config;
sử dụng MyMod;
BẮT ĐẦU {
if ($ Config {useithreads}) {
# Chúng tôi có chủ đề
yêu cầu MyMod_threaded;
nhập MyMod_threaded;
} Else {
yêu cầu MyMod_unthreaded;
nhập MyMod_unthreaded;
}
}
Vì mã chạy cả có và không có luồng thường khá lộn xộn, tốt nhất là
cô lập mã dành riêng cho luồng trong mô-đun riêng của nó. Trong ví dụ của chúng tôi ở trên, đó là những gì
"MyMod_threaded" là và nó chỉ được nhập nếu chúng ta đang chạy trên Perl phân luồng.
A Chú thích về các Các ví dụ
Trong tình huống thực tế, cần lưu ý rằng tất cả các luồng được thực thi xong trước khi
chương trình thoát. Chăm sóc đó có không được đưa vào các ví dụ này vì lợi ích của
sự đơn giản. Chạy các ví dụ này as is sẽ tạo ra các thông báo lỗi, thường là do
thực tế là vẫn có các luồng chạy khi chương trình thoát. Bạn không nên
báo động bởi điều này.
Tạo Chủ đề
Mô-đun luồng cung cấp các công cụ bạn cần để tạo luồng mới. Giống như bất kỳ khác
mô-đun, bạn cần nói với Perl rằng bạn muốn sử dụng nó; "sử dụng chủ đề;" nhập khẩu tất cả
những mảnh bạn cần để tạo các chủ đề cơ bản.
Cách đơn giản nhất, dễ hiểu nhất để tạo một chuỗi là với "create ()":
sử dụng chủ đề;
my $ thr = thread-> create (\ & sub1);
phụ phụ1 {
print ("Trong luồng \ n");
}
Phương thức "create ()" nhận tham chiếu đến một chương trình con và tạo một chuỗi mới
bắt đầu thực thi trong chương trình con được tham chiếu. Điều khiển sau đó chuyển cả hai cho chương trình con
và người gọi.
Nếu bạn cần, chương trình của bạn có thể chuyển các tham số cho chương trình con như một phần của chuỗi
khởi động. Chỉ cần bao gồm danh sách các tham số như một phần của lệnh gọi "thread-> create ()",
như thế này:
sử dụng chủ đề;
của tôi $ Param3 = 'foo';
my $ thr1 = thread-> create (\ & sub1, 'Param 1', 'Param 2', $ Param3);
@ParamList của tôi = (42, 'Xin chào', 3.14);
my $ thr2 = thread-> create (\ & sub1, @ParamList);
my $ thr3 = thread-> create (\ & sub1, qw (Param1 Param2 Param3));
phụ phụ1 {
@InboundParameters của tôi = @_;
print ("Trong luồng \ n");
print ('Có tham số>', join ('<>', @ InboundParameters), "<\ n");
}
Ví dụ cuối cùng minh họa một tính năng khác của luồng. Bạn có thể sinh ra một số
chủ đề sử dụng cùng một chương trình con. Mỗi luồng thực thi cùng một chương trình con, nhưng trong một
luồng riêng biệt với một môi trường riêng biệt và các đối số có khả năng tách biệt.
"new ()" là từ đồng nghĩa với "create ()".
Đợi Trong A Sợi chỉ Đến Ra
Vì các luồng cũng là các chương trình con, chúng có thể trả về các giá trị. Để đợi một chuỗi thoát ra
và trích xuất bất kỳ giá trị nào mà nó có thể trả về, bạn có thể sử dụng phương thức "join ()":
sử dụng chủ đề;
my ($ thr) = thread-> create (\ & sub1);
my @ReturnData = $ thr-> join ();
print ('Luồng được trả về', join (',', @ReturnData), "\ n");
sub sub1 {return ('Năm mươi sáu', 'foo', 2); }
Trong ví dụ trên, phương thức "join ()" trả về ngay sau khi luồng kết thúc. Ngoài
chờ một chuỗi kết thúc và thu thập bất kỳ giá trị nào mà chuỗi đó có thể có
trả về, "join ()" cũng thực hiện bất kỳ hoạt động dọn dẹp hệ điều hành nào cần thiết cho luồng. Dọn dẹp đó
có thể quan trọng, đặc biệt là đối với các chương trình chạy lâu tạo ra nhiều luồng. Nếu như
bạn không muốn các giá trị trả về và không muốn đợi chuỗi kết thúc, bạn
nên gọi phương thức "detach ()" thay thế, như được mô tả tiếp theo.
LƯU Ý: Trong ví dụ trên, chuỗi trả về một danh sách, do đó yêu cầu rằng chuỗi
lệnh gọi tạo được thực hiện trong ngữ cảnh danh sách (tức là "của tôi ($ thr)"). Xem "$ thr->tham gia()"trong chủ đề
và "THREAD CONTEXT" trong luồng để biết thêm chi tiết về ngữ cảnh luồng và giá trị trả về.
Bỏ qua A Sợi chỉ
"join ()" thực hiện ba việc: nó đợi một chuỗi thoát ra, dọn dẹp sau đó và trả về
bất kỳ dữ liệu nào mà chủ đề có thể đã tạo ra. Nhưng nếu bạn không quan tâm đến chuỗi
trả về giá trị, và bạn không thực sự quan tâm khi chuỗi kết thúc? Tất cả những gì bạn muốn là cho
chủ đề để được dọn dẹp sau khi hoàn thành.
Trong trường hợp này, bạn sử dụng phương thức "detach ()". Khi một chuỗi được tách ra, nó sẽ chạy cho đến khi
Xong rồi; thì Perl sẽ tự động dọn dẹp sau đó.
sử dụng chủ đề;
my $ thr = thread-> create (\ & sub1); # Sinh ra sợi
$ thr-> detach (); # Bây giờ chúng tôi chính thức không quan tâm nữa
ngủ(15); # Để chuỗi chạy một lúc
phụ phụ1 {
số $ của tôi = 0;
trong khi (1) {
$ đếm ++;
print ("\ $ count là $ count \ n");
ngủ(1);
}
}
Khi một chuỗi được tách ra, nó có thể không được tham gia và bất kỳ dữ liệu trả về nào mà nó có thể có
được sản xuất (nếu nó đã được thực hiện và đang chờ tham gia) bị mất.
"detach ()" cũng có thể được gọi như một phương thức lớp để cho phép một luồng tự tách ra:
sử dụng chủ đề;
my $ thr = thread-> create (\ & sub1);
phụ phụ1 {
luồng-> detach ();
# Làm nhiều việc hơn
}
Quy trình xét duyệt và Sợi chỉ Chấm dứt hợp đồng
Với các chủ đề, người ta phải cẩn thận để đảm bảo rằng tất cả chúng đều có cơ hội chạy đến hoàn thành,
giả sử đó là những gì bạn muốn.
Một hành động kết thúc một quy trình sẽ kết thúc tất cả các các chủ đề đang chạy. die () và lối ra()
có thuộc tính này và perl thực hiện thoát khi luồng chính thoát ra, có lẽ ngầm hiểu
bằng cách rơi ra cuối mã của bạn, ngay cả khi đó không phải là những gì bạn muốn.
Ví dụ về trường hợp này, mã này in ra thông báo "Perl đã thoát với chuỗi hoạt động:
2 đang chạy và không liên kết ":
sử dụng chủ đề;
my $ thr1 = thread-> new (\ & thrsub, "test1");
my $ thr2 = thread-> new (\ & thrsub, "test2");
phụ thrsub {
của tôi ($ message) = @_;
ngủ 1;
in "chuỗi $ message \ n";
}
Nhưng khi các dòng sau được thêm vào cuối:
$ thr1-> tham gia ();
$ thr2-> tham gia ();
nó in ra hai dòng đầu ra, một kết quả có lẽ hữu ích hơn.
Chủ đề và Ngày
Bây giờ chúng ta đã đề cập đến những điều cơ bản về chủ đề, đã đến lúc dành cho chủ đề tiếp theo của chúng ta: Dữ liệu.
Luồng giới thiệu một số phức tạp đối với việc truy cập dữ liệu mà các chương trình không phân luồng
không bao giờ cần phải lo lắng về.
Chia sẻ và Chưa chia sẻ Ngày
Sự khác biệt lớn nhất giữa Perl nó và phân luồng kiểu 5.005 cũ hoặc cho
vấn đề đó, đối với hầu hết các hệ thống phân luồng khác, là theo mặc định, không có dữ liệu nào
đã chia sẻ. Khi một chuỗi Perl mới được tạo, tất cả dữ liệu được liên kết với chuỗi hiện tại
được sao chép vào luồng mới, và sau đó là riêng tư đối với luồng mới đó! Đây là
tương tự như những gì xảy ra khi một quy trình Unix phân tách, ngoại trừ trong trường hợp này,
dữ liệu chỉ được sao chép vào một phần khác của bộ nhớ trong cùng một quy trình chứ không phải
fork thực sự đang diễn ra.
Tuy nhiên, để tận dụng luồng, người ta thường muốn các luồng chia sẻ ít nhất một số
dữ liệu giữa chúng. Điều này được thực hiện với chủ đề :: mô-đun được chia sẻ và ": đã chia sẻ"
thuộc tính:
sử dụng chủ đề;
sử dụng chủ đề :: shared;
$ foo của tôi: shared = 1;
thanh $ của tôi = 1;
thread-> create (sub {$ foo ++; $ bar ++;}) -> join ();
print ("$ foo \ n"); # Bản in 2 vì $ foo được chia sẻ
print ("$ bar \ n"); # Bản in 1 vì $ bar không được chia sẻ
Trong trường hợp một mảng được chia sẻ, tất cả các phần tử của mảng được chia sẻ và đối với một hàm băm được chia sẻ,
tất cả các khóa và giá trị được chia sẻ. Điều này đặt ra những hạn chế đối với những gì có thể được chỉ định cho
mảng được chia sẻ và phần tử băm: chỉ các giá trị đơn giản hoặc tham chiếu đến các biến được chia sẻ là
được phép - điều này để biến riêng tư không thể vô tình bị chia sẻ. Xấu
việc gán sẽ khiến luồng bị chết. Ví dụ:
sử dụng chủ đề;
sử dụng chủ đề :: shared;
$ var = 1 của tôi;
$ svar của tôi: shared = 2;
% băm của tôi: đã chia sẻ;
... tạo một số chủ đề ...
$ băm {a} = 1; # Tất cả các chuỗi được xem đều tồn tại ($ hash {a})
# và $ băm {a} == 1
$ băm {a} = $ var; # okay - copy-by-value: hiệu ứng tương tự như trước
$ băm {a} = $ svar; # okay - copy-by-value: hiệu ứng tương tự như trước
$ băm {a} = \ $ svar; # okay - tham chiếu đến một biến được chia sẻ
$ băm {a} = \ $ var; # Điều này sẽ chết
xóa ($ băm {a}); # okay - tất cả các chuỗi sẽ thấy! tồn tại ($ hash {a})
Lưu ý rằng một biến được chia sẻ đảm bảo rằng nếu hai hoặc nhiều chuỗi cố gắng sửa đổi nó tại
đồng thời, trạng thái bên trong của biến sẽ không bị hỏng. Tuy nhiên, ở đó
không có đảm bảo nào ngoài điều này, như được giải thích trong phần tiếp theo.
Sợi chỉ Cạm bẫy: Chủng tộc
Trong khi chủ đề mang lại một loạt công cụ hữu ích mới, chúng cũng mang lại một số cạm bẫy. Một
cạm bẫy là điều kiện của cuộc đua:
sử dụng chủ đề;
sử dụng chủ đề :: shared;
$ x của tôi: shared = 1;
my $ thr1 = thread-> create (\ & sub1);
my $ thr2 = thread-> create (\ & sub2);
$ thr1-> tham gia ();
$ thr2-> tham gia ();
print ("$ x \ n");
sub sub1 {my $ foo = $ x; $ x = $ foo + 1; }
sub sub2 {my $ bar = $ x; $ x = $ thanh + 1; }
Bạn nghĩ $ x sẽ là gì? Câu trả lời, thật không may, là it phụ thuộc. Cả "sub1 ()" và
"sub2 ()" truy cập vào biến tổng thể $ x, một lần để đọc và một lần để ghi. Phụ thuộc vào
các yếu tố khác nhau, từ thuật toán lập lịch trình triển khai chuỗi của bạn đến giai đoạn của
moon, $ x có thể là 2 hoặc 3.
Điều kiện cuộc đua là do quyền truy cập không đồng bộ vào dữ liệu được chia sẻ. Không rõ ràng
đồng bộ hóa, không có cách nào để chắc chắn rằng không có gì xảy ra với dữ liệu được chia sẻ
giữa thời gian bạn truy cập nó và thời gian bạn cập nhật nó. Ngay cả đoạn mã đơn giản này
có khả năng xảy ra lỗi:
sử dụng chủ đề;
$ x của tôi: shared = 2;
$ y của tôi: đã chia sẻ;
$ z của tôi: được chia sẻ;
my $ thr1 = thread-> create (sub {$ y = $ x; $ x = $ y + 1;});
my $ thr2 = thread-> create (sub {$ z = $ x; $ x = $ z + 1;});
$ thr1-> tham gia ();
$ thr2-> tham gia ();
Cả hai luồng đều truy cập $ x. Mỗi chuỗi có thể bị gián đoạn bất kỳ lúc nào, hoặc
được thực hiện theo bất kỳ thứ tự nào. Cuối cùng, $ x có thể là 3 hoặc 4 và cả $ y và $ z có thể là 2
hoặc 3.
Ngay cả "$ x + = 5" hoặc "$ x ++" không được đảm bảo là nguyên tử.
Bất cứ khi nào chương trình của bạn truy cập dữ liệu hoặc tài nguyên mà các luồng khác có thể truy cập,
bạn phải thực hiện các bước để điều phối việc truy cập hoặc rủi ro dữ liệu không nhất quán và các điều kiện đua.
Lưu ý rằng Perl sẽ bảo vệ bên trong của nó khỏi các điều kiện chủng tộc của bạn, nhưng nó sẽ không bảo vệ
bạn từ bạn.
Đồng bộ hóa và điều khiển
Perl cung cấp một số cơ chế để điều phối các tương tác giữa chúng và
dữ liệu của họ, để tránh các điều kiện chủng tộc và những thứ tương tự. Một số trong số này được thiết kế để giống
các kỹ thuật phổ biến được sử dụng trong thư viện luồng như "pthreads"; những người khác là Perl-
riêng biệt. Thông thường, các kỹ thuật tiêu chuẩn rất vụng về và khó thực hiện đúng (chẳng hạn như
điều kiện đang chờ). Nếu có thể, thường sẽ dễ dàng hơn khi sử dụng các kỹ thuật Perlish như
hàng đợi, giúp loại bỏ một số công việc khó khăn liên quan.
Kiểm soát truy cập: Khóa()
Hàm "lock ()" nhận một biến được chia sẻ và đặt một khóa trên đó. Không có chủ đề nào khác có thể
khóa biến cho đến khi biến được mở khóa bằng ren giữ khóa. Mở khóa
xảy ra tự động khi chuỗi khóa thoát ra khỏi khối có chứa lệnh gọi đến
hàm "lock ()". Sử dụng "lock ()" rất đơn giản: Ví dụ này có một số
các chuỗi thực hiện một số phép tính song song và thỉnh thoảng cập nhật tổng số đang chạy:
sử dụng chủ đề;
sử dụng chủ đề :: shared;
tổng số tiền của tôi: shared = 0;
phụ calc {
trong khi (1) {
kết quả $ của tôi;
# (... thực hiện một số phép tính và đặt $ result ...)
{
khóa (tổng số $); # Chặn cho đến khi chúng tôi lấy được khóa
$ tổng + = $ kết quả;
} # Khóa được phát hành hoàn toàn ở cuối phạm vi
cuối cùng nếu $ kết quả == 0;
}
}
my $ thr1 = thread-> create (\ & calc);
my $ thr2 = thread-> create (\ & calc);
my $ thr3 = thread-> create (\ & calc);
$ thr1-> tham gia ();
$ thr2-> tham gia ();
$ thr3-> tham gia ();
print ("tổng = $ tổng \ n");
"lock ()" chặn luồng cho đến khi biến bị khóa khả dụng. Khi "khóa ()"
trả về, luồng của bạn có thể chắc chắn rằng không có luồng nào khác có thể khóa biến đó cho đến khi
khối chứa khóa thoát.
Điều quan trọng cần lưu ý là các khóa không ngăn quyền truy cập vào biến được đề cập, chỉ
khóa nỗ lực. Điều này phù hợp với truyền thống lịch sự lâu đời của Perl
lập trình và khóa tệp tư vấn mà "bầy đàn ()" cung cấp cho bạn.
Bạn có thể khóa mảng và băm cũng như vô hướng. Tuy nhiên, khóa một mảng sẽ không
chặn các khóa tiếp theo trên các phần tử mảng, chỉ khóa các lần thử trên chính mảng.
Các khóa là đệ quy, có nghĩa là một luồng không sao để khóa một biến nhiều hơn một lần.
Khóa sẽ kéo dài cho đến khi "lock ()" ngoài cùng trên biến vượt ra khỏi phạm vi. Vì
thí dụ:
$ x của tôi: được chia sẻ;
làm đi();
phụ doit {
{
{
khóa ($ x); # Chờ khóa
khóa ($ x); # KHÔNG - chúng tôi đã có khóa
{
khóa ($ x); # KHÔNG RA ĐÂU
{
khóa ($ x); # KHÔNG RA ĐÂU
lockit_some_more ();
}
}
} # *** Mở khóa ngầm tại đây ***
}
}
phụ lockit_some_more {
khóa ($ x); # KHÔNG RA ĐÂU
} # Không có gì xảy ra ở đây
Lưu ý rằng không có hàm "unlock ()" - cách duy nhất để mở khóa một biến là cho phép
nó vượt ra khỏi phạm vi.
Một khóa có thể được sử dụng để bảo vệ dữ liệu chứa trong biến đang bị khóa, hoặc
nó có thể được sử dụng để bảo vệ một cái gì đó khác, chẳng hạn như một đoạn mã. Trong trường hợp thứ hai này,
biến được đề cập không chứa bất kỳ dữ liệu hữu ích nào và chỉ tồn tại cho mục đích
bị khóa. Về mặt này, biến hoạt động giống như mutexes và semaphores cơ bản
của các thư viện luồng truyền thống.
A Sợi chỉ Cạm bẫy: Bế tắc
Khóa là một công cụ tiện dụng để đồng bộ hóa quyền truy cập vào dữ liệu và sử dụng chúng đúng cách là chìa khóa
đến dữ liệu được chia sẻ an toàn. Thật không may, ổ khóa không phải là không có nguy hiểm, đặc biệt là khi
nhiều khóa có liên quan. Hãy xem xét đoạn mã sau:
sử dụng chủ đề;
$ x của tôi: shared = 4;
$ y của tôi: shared = 'foo';
my $ thr1 = thread-> create (sub {
khóa ($ x);
ngủ(20);
khóa ($ y);
});
my $ thr2 = thread-> create (sub {
khóa ($ y);
ngủ(20);
khóa ($ x);
});
Chương trình này có thể sẽ bị treo cho đến khi bạn giết nó. Cách duy nhất để nó không bị treo là nếu một
của hai chủ đề có được cả hai khóa trước. Một phiên bản được đảm bảo để treo nhiều hơn
phức tạp, nhưng nguyên tắc là như nhau.
Chuỗi đầu tiên sẽ lấy một khóa trên $ x, sau đó, sau khi tạm dừng trong đó chuỗi thứ hai
chủ đề có lẽ đã có thời gian để thực hiện một số công việc, hãy cố gắng lấy một khóa trên $ y. Trong khi đó,
luồng thứ hai lấy một ổ khóa trên $ y, rồi sau đó cố gắng lấy một ổ khóa trên $ x. Khóa thứ hai
nỗ lực cho cả hai luồng sẽ bị chặn, mỗi luồng chờ người kia giải phóng khóa của nó.
Điều kiện này được gọi là bế tắc và nó xảy ra bất cứ khi nào hai hoặc nhiều luồng đang cố gắng
để lấy khóa các tài nguyên mà những người khác sở hữu. Mỗi chuỗi sẽ chặn, chờ
khác để phát hành một khóa trên một tài nguyên. Tuy nhiên, điều đó không bao giờ xảy ra vì chuỗi với
bản thân tài nguyên đang đợi một khóa được phát hành.
Có một số cách để xử lý loại vấn đề này. Cách tốt nhất là luôn có
tất cả các chủ đề có được các khóa theo thứ tự chính xác. Ví dụ: nếu bạn khóa các biến
$ x, $ y và $ z, luôn khóa $ x trước $ y và $ y trước $ z. Tốt nhất bạn nên giữ lấy
ổ khóa trong một khoảng thời gian ngắn để giảm thiểu rủi ro tắc nghẽn.
Các nguyên thủy đồng bộ hóa khác được mô tả bên dưới có thể gặp phải các vấn đề tương tự.
Hàng đợi: Đi qua Ngày Xung quanh
Hàng đợi là một đối tượng an toàn theo luồng đặc biệt cho phép bạn đặt dữ liệu vào một đầu và lấy nó ra
khác mà không phải lo lắng về các vấn đề đồng bộ hóa. Họ đẹp
đơn giản và trông như thế này:
sử dụng chủ đề;
sử dụng Thread :: Queue;
my $ DataQueue = Thread :: Queue-> new ();
my $ thr = thread-> create (sub {
while (my $ DataElement = $ DataQueue-> dequeue ()) {
print ("Đã đưa $ DataElement ra khỏi hàng đợi \ n");
}
});
$ DataQueue->enqueue(12);
$ DataQueue-> enqueue ("A", "B", "C");
ngủ(10);
$ DataQueue-> enqueue (undef);
$ thr-> tham gia ();
Bạn tạo hàng đợi với "Thread :: Queue-> new ()". Sau đó, bạn có thể thêm danh sách các đại lượng vô hướng vào
kết thúc bằng "enqueue ()" và vô hướng cửa sổ bật lên phía trước nó bằng "dequeue ()". Một hàng đợi
không có kích thước cố định và có thể phát triển khi cần thiết để giữ mọi thứ được đẩy lên đó.
Nếu một hàng đợi trống, "dequeue ()" sẽ chặn cho đến khi một luồng khác xếp hàng thứ gì đó. Điều này
làm cho hàng đợi trở nên lý tưởng cho các vòng lặp sự kiện và các giao tiếp khác giữa các luồng.
Dấu hiệu: Đồng bộ hóa Ngày Truy Cập
Semaphores là một loại cơ chế khóa chung. Ở dạng cơ bản nhất, chúng cư xử
rất giống các đại lượng vô hướng có thể khóa, ngoại trừ việc chúng không thể giữ dữ liệu và chúng phải
được mở khóa rõ ràng. Ở dạng nâng cao, chúng hoạt động giống như một loại bộ đếm và có thể
cho phép nhiều chủ đề có khóa tại bất kỳ thời điểm nào.
Cơ bản đèn hiệu
Semaphores có hai phương thức, "down ()" và "up ()": "down ()" giảm số lượng tài nguyên,
trong khi "up ()" tăng nó lên. Các lệnh gọi đến "down ()" sẽ bị chặn nếu số lượng hiện tại của semaphore
sẽ giảm xuống dưới XNUMX. Chương trình này đưa ra một minh chứng nhanh chóng:
sử dụng chủ đề;
sử dụng Thread :: Semaphore;
my $ semaphore = Thread :: Semaphore-> new ();
$ GlobalVariable của tôi: shared = 0;
$ thr1 = thread-> create (\ & sample_sub, 1);
$ thr2 = thread-> create (\ & sample_sub, 2);
$ thr3 = thread-> create (\ & sample_sub, 3);
mẫu phụ_sub {
của tôi $ SubNumber = shift (@_);
$ TryCount của tôi = 10;
$ LocalCopy của tôi;
ngủ(1);
trong khi ($ TryCount--) {
$ semaphore-> down ();
$ LocalCopy = $ GlobalVariable;
print ("$ TryCount cố gắng còn lại cho $ SubNumber con"
. "(\ $ GlobalVariable là $ GlobalVariable) \ n");
ngủ(2);
$ LocalCopy ++;
$ GlobalVariable = $ LocalCopy;
$ semaphore-> up ();
}
}
$ thr1-> tham gia ();
$ thr2-> tham gia ();
$ thr3-> tham gia ();
Ba lệnh gọi của chương trình con đều hoạt động đồng bộ. Tuy nhiên, semaphore làm cho
chắc chắn rằng chỉ có một luồng đang truy cập biến toàn cục cùng một lúc.
Nâng cao đèn hiệu
Theo mặc định, các semaphores hoạt động giống như các ổ khóa, chỉ cho phép một chuỗi "down ()" chúng tại một thời điểm.
Tuy nhiên, có những cách sử dụng khác cho semaphores.
Mỗi semaphore có một bộ đếm gắn liền với nó. Theo mặc định, semaphores được tạo với
bộ đếm được đặt thành một, "down ()" giảm bộ đếm đi một và "lên ()" tăng một.
Tuy nhiên, chúng tôi có thể ghi đè bất kỳ hoặc tất cả các giá trị mặc định này chỉ bằng cách chuyển
các giá trị:
sử dụng chủ đề;
sử dụng Thread :: Semaphore;
$ semaphore của tôi = Chủ đề :: Semaphore->mới(5);
# Tạo một semaphore với bộ đếm được đặt thành năm
my $ thr1 = thread-> create (\ & sub1);
my $ thr2 = thread-> create (\ & sub1);
phụ phụ1 {
$ semaphore->xuống(5); # Giảm bộ đếm đi năm
# Làm công việc ở đây
$ semaphore->up(5); # Tăng bộ đếm lên năm
}
$ thr1-> detach ();
$ thr2-> detach ();
Nếu "down ()" cố gắng giảm bộ đếm xuống dưới XNUMX, nó sẽ chặn cho đến khi bộ đếm
đủ lớn. Lưu ý rằng mặc dù một semaphore có thể được tạo với số lượng bắt đầu bằng XNUMX,
bất kỳ "up ()" hoặc "down ()" nào luôn thay đổi bộ đếm ít nhất một và như vậy
"$ semaphore->xuống(0) "giống với" $ semaphore->xuống(1) ”.
Tất nhiên, câu hỏi đặt ra là tại sao bạn lại làm điều gì đó như thế này? Tại sao phải tạo semaphore
với số lượng bắt đầu không phải là một, hoặc tại sao lại giảm hoặc tăng nó lên nhiều hơn một?
Câu trả lời là sự sẵn có của tài nguyên. Nhiều tài nguyên mà bạn muốn quản lý quyền truy cập
có thể được sử dụng an toàn bởi nhiều chủ đề cùng một lúc.
Ví dụ, hãy lấy một chương trình điều khiển GUI. Nó có một semaphore mà nó sử dụng để
đồng bộ hóa quyền truy cập vào màn hình, vì vậy chỉ có một luồng được vẽ cùng một lúc. Tiện dụng, nhưng
tất nhiên bạn không muốn bất kỳ luồng nào bắt đầu vẽ cho đến khi mọi thứ được thiết lập đúng cách. Trong
trong trường hợp này, bạn có thể tạo một semaphore với bộ đếm được đặt thành XNUMX và lên nó khi mọi thứ
đã sẵn sàng để vẽ.
Semaphores có bộ đếm lớn hơn một cũng hữu ích để thiết lập hạn ngạch. Nói,
ví dụ: bạn có một số luồng có thể thực hiện I / O cùng một lúc. Bạn không muốn
Tuy nhiên, tất cả các chuỗi đọc hoặc viết cùng một lúc, vì điều đó có khả năng phá hủy
Các kênh I / O hoặc làm cạn kiệt hạn ngạch xử lý tệp trong quy trình của bạn. Bạn có thể sử dụng một semaphore
được khởi tạo với số lượng yêu cầu I / O đồng thời (hoặc tệp đang mở) mà bạn muốn ở bất kỳ
một lần và yêu cầu các chủ đề của bạn tự động chặn và bỏ chặn.
Các khoảng tăng hoặc giảm lớn hơn rất hữu ích trong những trường hợp mà một chuỗi cần kiểm tra
hoặc trả lại một số tài nguyên cùng một lúc.
Đợi cho a Điều kiện
Các hàm "cond_wait ()" và "cond_signal ()" có thể được sử dụng cùng với các khóa để
thông báo cho các luồng hợp tác rằng một tài nguyên đã có sẵn. Chúng rất giống nhau trong
sử dụng các chức năng được tìm thấy trong "pthreads". Tuy nhiên, đối với hầu hết các mục đích, hàng đợi đơn giản hơn để
sử dụng và trực quan hơn. Xem chủ đề :: đã chia sẻ để biết thêm chi tiết.
Cho up điều khiển
Đôi khi bạn có thể thấy hữu ích khi có một luồng rõ ràng từ bỏ CPU để
một chủ đề khác. Bạn có thể đang làm một việc gì đó đòi hỏi nhiều kỹ năng xử lý và muốn đảm bảo rằng
chuỗi giao diện người dùng được gọi thường xuyên. Bất kể, có những lúc bạn
có thể muốn một luồng từ bỏ bộ xử lý.
Gói phân luồng của Perl cung cấp hàm "output ()" để thực hiện điều này. "output ()" là
khá đơn giản và hoạt động như thế này:
sử dụng chủ đề;
vòng lặp phụ {
$ thread = shift của tôi;
$ foo của tôi = 50;
while ($ foo--) {print ("Trong luồng $ thread \ n"); }
luồng-> output ();
$ foo = 50;
while ($ foo--) {print ("Trong luồng $ thread \ n"); }
}
my $ thr1 = thread-> create (\ & loop, 'first');
my $ thr2 = thread-> create (\ & loop, 'second');
my $ thr3 = thread-> create (\ & loop, 'third');
Điều quan trọng cần nhớ là "yif ()" chỉ là một gợi ý để từ bỏ CPU, nó phụ thuộc
trên phần cứng, hệ điều hành và thư viện luồng của bạn những gì thực sự xảy ra. On nhiều hoạt động
hệ thống, năng suất () is a không ra đâu. Do đó, điều quan trọng cần lưu ý là không nên xây dựng
lập lịch của các chủ đề xung quanh các cuộc gọi "output ()". Nó có thể hoạt động trên nền tảng của bạn nhưng
nó sẽ không hoạt động trên nền tảng khác.
Tổng quan Sợi chỉ Tiện ích Thói quen
Chúng tôi đã đề cập đến các phần workhorse của gói luồng của Perl và với những công cụ này, bạn
sẽ tốt trên con đường của bạn để viết mã luồng và các gói. Có một số hữu ích
những mảnh nhỏ không thực sự phù hợp với bất kỳ nơi nào khác.
Điều gì Sợi chỉ Am I Trong?
Phương thức lớp "thread-> self ()" cung cấp cho chương trình của bạn một cách để lấy một đối tượng
đại diện cho chuỗi hiện tại của nó. Bạn có thể sử dụng đối tượng này theo cách tương tự như
những cái được trả về từ quá trình tạo luồng.
Sợi chỉ ID
"tid ()" là một phương thức đối tượng luồng trả về ID luồng của đối tượng luồng
đại diện. ID luồng là số nguyên, với luồng chính trong chương trình là 0.
Hiện tại Perl chỉ định một TID duy nhất cho mọi luồng từng được tạo trong chương trình của bạn,
chỉ định luồng đầu tiên được tạo TID là 1 và tăng TID lên 1 cho mỗi
chủ đề mới đã được tạo. Khi được sử dụng như một phương thức lớp, "thread-> tid ()" có thể được sử dụng bởi
để lấy TID của chính nó.
Có Kia là Chủ đề Sản phẩm Giống nhau?
Phương thức "bằng ()" nhận hai đối tượng luồng và trả về true nếu các đối tượng đại diện
cùng một chủ đề và sai nếu chúng không có.
Các đối tượng luồng cũng có một so sánh "==" quá tải để bạn có thể thực hiện so sánh trên
chúng như bạn làm với các đối tượng bình thường.
Điều gì Chủ đề Có Đang chạy?
"thread-> list ()" trả về danh sách các đối tượng chuỗi, một đối tượng cho mỗi chủ đề hiện đang
đang chạy và không bị tách rời. Tiện dụng cho một số việc, bao gồm cả việc dọn dẹp khi kết thúc
của chương trình của bạn (tất nhiên là từ chuỗi Perl chính):
# Lặp lại tất cả các chuỗi
foreach $ thr (thread-> list ()) {của tôi
$ thr-> tham gia ();
}
Nếu một số luồng vẫn chưa chạy xong khi luồng Perl chính kết thúc, Perl sẽ cảnh báo
bạn về nó và chết, vì Perl không thể tự dọn dẹp trong khi những người khác
chủ đề đang chạy.
LƯU Ý: Luồng Perl chính (luồng 0) nằm trong một tách ra trạng thái, và do đó không xuất hiện trong
danh sách được trả về bởi "thread-> list ()".
A Hoàn thành Ví dụ
Bạn bối rối chưa? Đã đến lúc một chương trình ví dụ hiển thị một số điều chúng tôi đã đề cập.
Chương trình này tìm các số nguyên tố bằng cách sử dụng chủ đề.
1 #!/ usr / bin / perl
2 # prime-pthread, do Tom Christiansen cung cấp
3
4 sử dụng nghiêm ngặt;
5 cảnh báo sử dụng;
6
7 sử dụng chủ đề;
8 sử dụng Thread :: Queue;
9
10 phụ check_num {
11 của tôi ($ ngược dòng, $ cur_prime) = @_;
12 con $ của tôi;
13 my $ down = Thread :: Queue-> new ();
14 while (my $ num = $ up-> dequeue ()) {
15 tiếp theo trừ khi ($ num% $ cur_prime);
16 nếu ($ kid) {
17 $ xuống dòng-> enqueue ($ num);
18} khác {
19 print ("Số nguyên tố tìm thấy: $ num \ n");
20 $ kid = thread-> create (\ & check_num, $ down, $ num);
21 if (! $ Kid) {
22 warning ("Xin lỗi. Hết luồng. \ N");
23 cuối cùng;
24}
25}
26}
27 nếu ($ kid) {
28 $ xuống dòng-> enqueue (undef);
29 $ kid-> join ();
30}
31}
32
33 my $ stream = Thread :: Queue-> new (3..1000, undef);
34 check_num ($ stream, 2);
Chương trình này sử dụng mô hình đường ống để tạo ra các số nguyên tố. Mỗi chủ đề trong
đường ống dẫn có một hàng đợi đầu vào cung cấp các số được kiểm tra, một số nguyên tố mà nó
chịu trách nhiệm và một hàng đợi đầu ra mà nó đưa vào đó các số đã không thành công
kiểm tra. Nếu chuỗi có một số không được kiểm tra và không có chuỗi con,
thì chủ đề chắc hẳn đã tìm được một số nguyên tố mới. Trong trường hợp đó, một chuỗi con mới là
được tạo cho số nguyên tố đó và bị mắc kẹt ở cuối đường ống.
Điều này có thể nghe hơi khó hiểu hơn thực tế, vì vậy chúng ta hãy xem xét vấn đề này
chương trình từng phần và xem nó làm gì. (Đối với những người trong số các bạn có thể đang cố gắng
nhớ chính xác số nguyên tố là gì, đó là số chỉ chia hết cho
chính nó và 1.)
Phần lớn công việc được thực hiện bởi chương trình con "check_num ()", có tham chiếu đến
hàng đợi đầu vào của nó và một số nguyên tố mà nó chịu trách nhiệm. Sau khi kéo đầu vào
hàng đợi và số nguyên tố mà chương trình con đang kiểm tra (dòng 11), chúng ta tạo một hàng đợi mới (dòng
13) và dành một đại lượng vô hướng cho luồng mà chúng ta có thể sẽ tạo sau này (dòng 12).
Vòng lặp while từ dòng 14 đến dòng 26 lấy một vô hướng ra khỏi hàng đợi đầu vào và kiểm tra
chống lại nguyên tố mà chủ đề này chịu trách nhiệm. Dòng 15 kiểm tra xem có
phần dư khi chúng ta chia số cần kiểm tra cho số nguyên tố của chúng ta. Nếu có,
số không được chia hết cho số nguyên tố của chúng ta, vì vậy chúng ta cần chuyển nó cho
luồng tiếp theo nếu chúng ta đã tạo một (dòng 17) hoặc tạo một luồng mới nếu chúng ta chưa tạo.
Tạo luồng mới là dòng 20. Chúng tôi chuyển cho nó một tham chiếu đến hàng đợi mà chúng tôi có
đã tạo và số nguyên tố mà chúng tôi đã tìm thấy. Trong các dòng từ 21 đến 24, chúng tôi kiểm tra để đảm bảo
rằng chuỗi mới của chúng tôi đã được tạo và nếu không, chúng tôi ngừng kiểm tra bất kỳ số nào còn lại trong
xếp hàng.
Cuối cùng, khi vòng lặp kết thúc (vì chúng tôi nhận được 0 hoặc "undef" trong hàng đợi,
coi như một thông báo chấm dứt), chúng tôi chuyển thông báo cho con mình và đợi nó
thoát ra nếu chúng ta đã tạo một phần tử con (dòng 27 và 30).
Trong khi đó, trở lại chuỗi chính, đầu tiên chúng tôi tạo một hàng đợi (dòng 33) và xếp hàng tất cả
số từ 3 đến 1000 để kiểm tra, cộng với thông báo chấm dứt. Sau đó, tất cả những gì chúng ta phải làm để
lăn bóng là chuyển hàng đợi và số nguyên tố đầu tiên vào chương trình con "check_num ()"
(dòng 34).
Đó là cách nó hoạt động. Nó khá đơn giản; như với nhiều chương trình Perl, lời giải thích là
lâu hơn nhiều so với chương trình.
Khác nhau triển khai of chủ đề
Một số thông tin cơ bản về triển khai luồng từ quan điểm hệ điều hành. Có
ba danh mục cơ bản của luồng: luồng chế độ người dùng, luồng nhân và đa xử lý
các luồng nhân.
Các luồng ở chế độ người dùng là các luồng nằm hoàn toàn trong một chương trình và các thư viện của nó. Trong
mô hình này, hệ điều hành không biết gì về luồng. Theo như liên quan, quy trình của bạn là
chỉ là một quá trình.
Đây là cách dễ nhất để triển khai các luồng và là cách hầu hết các hệ điều hành bắt đầu. Cái lớn
nhược điểm là, vì hệ điều hành không biết gì về các luồng, nếu một luồng chặn chúng
tất cả làm. Các hoạt động chặn điển hình bao gồm hầu hết các cuộc gọi hệ thống, hầu hết các I / O và những thứ như
"ngủ()".
Các luồng nhân là bước tiếp theo trong quá trình phát triển luồng. Hệ điều hành biết về các luồng hạt nhân,
và thực hiện các khoản phụ cấp cho họ. Sự khác biệt chính giữa luồng nhân và người dùng-
luồng chế độ đang chặn. Với các luồng nhân, những thứ chặn một luồng đơn lẻ không
chặn các chủ đề khác. Đây không phải là trường hợp xảy ra với các luồng chế độ người dùng, nơi hạt nhân khối
ở cấp quy trình chứ không phải cấp luồng.
Đây là một bước tiến lớn và có thể cung cấp cho một chương trình phân luồng hiệu suất cao hơn
chương trình không phân luồng. Ví dụ: các luồng chặn thực hiện I / O sẽ không chặn
chủ đề đang làm những việc khác. Mỗi quy trình vẫn chỉ có một luồng chạy tại
tuy nhiên, một lần, bất kể hệ thống có thể có bao nhiêu CPU.
Vì luồng hạt nhân có thể làm gián đoạn một luồng bất kỳ lúc nào, chúng sẽ khám phá ra một số
các giả định khóa ngầm mà bạn có thể thực hiện trong chương trình của mình. Ví dụ, một cái gì đó như
đơn giản như "$ x = $ x + 2" có thể hoạt động không thể đoán trước với các luồng nhân nếu $ x hiển thị với
các chủ đề khác, vì một chủ đề khác có thể đã thay đổi $ x giữa thời điểm nó được tìm nạp vào
bên tay phải và thời gian giá trị mới được lưu trữ.
Các luồng nhân đa xử lý là bước cuối cùng trong hỗ trợ luồng. Với bộ xử lý đa
luồng nhân trên máy có nhiều CPU, hệ điều hành có thể lập lịch hai hoặc nhiều luồng để
chạy đồng thời trên các CPU khác nhau.
Điều này có thể tăng hiệu suất nghiêm trọng cho chương trình phân luồng của bạn, vì nhiều
luồng sẽ được thực thi cùng một lúc. Tuy nhiên, như một sự cân bằng, bất kỳ sự cằn nhằn nào
các vấn đề đồng bộ hóa có thể không hiển thị với các luồng nhân cơ bản sẽ xuất hiện
với một sự trả thù.
Ngoài các mức độ khác nhau của hệ điều hành tham gia vào các luồng, các hệ điều hành khác nhau (và
triển khai luồng khác nhau cho một hệ điều hành cụ thể) phân bổ chu kỳ CPU cho các luồng trong
những cách khác.
Hệ thống đa nhiệm hợp tác có các luồng đang chạy từ bỏ quyền kiểm soát nếu một trong hai điều
xảy ra. Nếu một luồng gọi một hàm năng suất, nó sẽ từ bỏ quyền kiểm soát. Nó cũng bỏ cuộc
kiểm soát xem luồng có thực hiện điều gì đó khiến nó bị chặn hay không, chẳng hạn như thực hiện I / O.
Trong triển khai đa nhiệm hợp tác, một luồng có thể bỏ đói tất cả các luồng khác cho CPU
thời gian nếu nó chọn.
Các hệ thống đa nhiệm dự phòng ngắt các luồng theo khoảng thời gian đều đặn trong khi hệ thống
quyết định luồng nào sẽ chạy tiếp theo. Trong hệ thống đa nhiệm phủ đầu, một chuỗi
thường sẽ không độc quyền CPU.
Trên một số hệ thống, có thể có các luồng hợp tác và ưu tiên chạy đồng thời.
(Các luồng chạy với mức độ ưu tiên thời gian thực thường hoạt động hợp tác, chẳng hạn như trong khi
các luồng đang chạy ở mức ưu tiên bình thường sẽ hoạt động trước.)
Hầu hết các hệ điều hành hiện đại ngày nay đều hỗ trợ đa nhiệm phủ đầu.
HIỆU QUẢ sự cân nhắc
Điều chính cần ghi nhớ khi so sánh Perl's nó đối với các mô hình luồng khác là
thực tế là đối với mỗi luồng mới được tạo, một bản sao hoàn chỉnh của tất cả các biến và dữ liệu
của luồng cha phải được thực hiện. Do đó, việc tạo luồng có thể khá tốn kém, cả hai
về mức độ sử dụng bộ nhớ và thời gian dành cho việc tạo. Cách lý tưởng để giảm những chi phí này
là có một số lượng tương đối ngắn các chủ đề tồn tại lâu dài, tất cả đều được tạo ra từ khá sớm
(trước khi luồng cơ sở đã tích lũy quá nhiều dữ liệu). Tất nhiên, điều này có thể không phải lúc nào cũng
có thể, vì vậy các thỏa hiệp phải được thực hiện. Tuy nhiên, sau khi một chuỗi đã được tạo,
hiệu suất và việc sử dụng thêm bộ nhớ sẽ khác một chút so với mã thông thường.
Cũng lưu ý rằng trong quá trình triển khai hiện tại, các biến được chia sẻ sử dụng nhiều bộ nhớ hơn một chút
và chậm hơn một chút so với các biến thông thường.
Quy trình-phạm vi Những thay đổi
Lưu ý rằng trong khi bản thân các luồng là các luồng thực thi riêng biệt và dữ liệu Perl là luồng-
riêng tư trừ khi được chia sẻ rõ ràng, các luồng có thể ảnh hưởng đến trạng thái phạm vi quy trình, ảnh hưởng đến
tất cả các chủ đề.
Ví dụ phổ biến nhất của việc này là thay đổi thư mục làm việc hiện tại bằng cách sử dụng "chdir ()".
Một luồng gọi "chdir ()" và thư mục làm việc của tất cả các luồng thay đổi.
Ví dụ thậm chí mạnh mẽ hơn về thay đổi phạm vi quy trình là "chroot ()": thư mục gốc của
tất cả các luồng thay đổi và không luồng nào có thể hoàn tác nó (trái ngược với "chdir ()").
Các ví dụ khác về thay đổi phạm vi quy trình bao gồm "umask ()" và thay đổi uids và gids.
Bạn đang nghĩ đến việc trộn "fork ()" và các chủ đề? Hãy nằm xuống và đợi cho đến khi cảm nhận
vượt qua. Lưu ý rằng ngữ nghĩa của "fork ()" khác nhau giữa các nền tảng. Ví dụ,
một số hệ thống Unix sao chép tất cả các luồng hiện tại vào quy trình con, trong khi những hệ thống khác chỉ
sao chép luồng có tên "fork ()". Bạn đã được cảnh báo!
Tương tự, việc trộn các tín hiệu và luồng có thể có vấn đề. Triển khai là nền tảng-
phụ thuộc và thậm chí ngữ nghĩa của POSIX có thể không như bạn mong đợi (và Perl thậm chí không
cung cấp cho bạn API POSIX đầy đủ). Ví dụ: không có cách nào để đảm bảo rằng một tín hiệu
được gửi đến một ứng dụng Perl đa luồng sẽ bị chặn bởi bất kỳ luồng cụ thể nào.
(Tuy nhiên, một tính năng được bổ sung gần đây cung cấp khả năng gửi tín hiệu giữa
chủ đề. Xem "TÍN HIỆU THREAD" trong chủ đề để biết thêm chi tiết.)
An toàn chủ đề of WELFARE Thư viện
Việc các lệnh gọi thư viện khác nhau có an toàn theo luồng hay không nằm ngoài sự kiểm soát của Perl. Gọi điện thường xuyên
bị không an toàn cho chuỗi bao gồm: "localtime ()", "gmtime ()", các hàm
tìm nạp thông tin người dùng, nhóm và mạng (chẳng hạn như "getgrent ()", "gethostent ()",
"getnetent ()", v.v.), "readdir ()", "rand ()" và "srand ()". Nói chung, gọi rằng
phụ thuộc vào một số trạng thái bên ngoài toàn cầu.
Nếu hệ thống Perl được biên dịch có các biến thể an toàn theo luồng của các lệnh gọi như vậy, chúng sẽ
được sử dụng. Ngoài ra, Perl còn phụ thuộc vào sự an toàn của chuỗi hoặc-độ an toàn của các cuộc gọi.
Vui lòng tham khảo tài liệu gọi thư viện C của bạn.
Trên một số nền tảng, giao diện thư viện an toàn luồng có thể bị lỗi nếu bộ đệm kết quả quá
nhỏ (ví dụ: cơ sở dữ liệu nhóm người dùng có thể khá lớn và người đăng nhập lại
các giao diện có thể phải mang theo một bản chụp đầy đủ của các cơ sở dữ liệu đó). Perl sẽ bắt đầu
với một bộ đệm nhỏ, nhưng hãy tiếp tục thử lại và phát triển bộ đệm kết quả cho đến khi có kết quả
vừa vặn. Nếu sự phát triển vô hạn này có vẻ không tốt vì lý do bảo mật hoặc tiêu thụ bộ nhớ
có thể biên dịch lại Perl với "PERL_REENTRANT_MAXSIZE" được xác định thành số byte tối đa
bạn sẽ cho phép.
Kết luận
Một hướng dẫn chuỗi hoàn chỉnh có thể lấp đầy một cuốn sách (và đã nhiều lần), nhưng với những gì chúng tôi đã
được đề cập trong phần giới thiệu này, bạn sẽ tốt trên con đường trở thành một Perl chủ đề
chuyên gia.
Sử dụng perlthrtut trực tuyến bằng các dịch vụ onworks.net