Computer >> Máy Tính >  >> Lập trình >> lập trình C

Chủ đề dựa trên ngôn ngữ và phân luồng ngầm

Phân luồng ngầm

Một cách để giải quyết những khó khăn và hỗ trợ tốt hơn cho việc thiết kế các ứng dụng đa luồng là chuyển việc tạo và quản lý phân luồng từ các nhà phát triển ứng dụng sang trình biên dịch và thư viện thời gian chạy. Đây, được gọi là phân luồng ngầm, là một xu hướng phổ biến hiện nay.

Chuỗi liên kết ngầm định chủ yếu là việc sử dụng các thư viện hoặc hỗ trợ ngôn ngữ khác để ẩn việc quản lý các luồng. Thư viện luồng ngầm phổ biến nhất là OpenMP, trong ngữ cảnh của C.

OpenMP là một tập hợp các chỉ thị trình biên dịch cũng như một API cho các chương trình được viết bằng C, C ++ hoặc FORTRAN cung cấp hỗ trợ cho lập trình song song trong môi trường bộ nhớ dùng chung. OpenMP xác định các vùng song song là các khối mã có thể chạy song song. Các nhà phát triển ứng dụng chèn các chỉ thị trình biên dịch vào mã của họ tại các vùng song song và các chỉ thị này hướng dẫn thư viện thời gian chạy OpenMP thực thi vùng song song. Chương trình C sau đây minh họa một chỉ thị trình biên dịch phía trên vùng song song chứa câu lệnh printf ():

Ví dụ

#include <omp.h>
#include <stdio.h>
int main(int argc, char *argv[]){
   /* sequential code */
   #pragma omp parallel{
      printf("I am a parallel region.");
   }
   /* sequential code */
   return 0;
}

Đầu ra

I am a parallel region.

Khi OpenMP gặp chỉ thị

#pragma omp parallel

Nó tạo ra bao nhiêu luồng là lõi xử lý trong hệ thống. Do đó, đối với hệ thống lõi kép, hai luồng được tạo ra, đối với hệ thống lõi tứ, bốn luồng được tạo; và kể từ đó trở đi. Sau đó, tất cả các luồng đồng thời thực hiện vùng song song. Khi mỗi luồng thoát ra khỏi vùng song song, nó sẽ bị kết thúc. OpenMP cung cấp một số chỉ thị bổ sung để chạy các vùng mã song song, bao gồm cả các vòng lặp song song.

Ngoài việc cung cấp các chỉ thị cho song song, OpenMP cho phép các nhà phát triển lựa chọn giữa một số cấp độ song song. Ví dụ:họ có thể đặt số lượng chủ đề theo cách thủ công. Nó cũng cho phép các nhà phát triển xác định xem dữ liệu được chia sẻ giữa các luồng hay là riêng tư đối với một luồng. OpenMP có sẵn trên một số trình biên dịch mã nguồn mở và thương mại cho các hệ thống Linux, Windows và Mac OS X.

Grand Central Dispatch (GCD)

Grand Central Dispatch (GCD) —một công nghệ dành cho hệ điều hành Mac OS X và iOS của Apple — là sự kết hợp của các phần mở rộng cho ngôn ngữ C, một API và thư viện thời gian chạy cho phép các nhà phát triển ứng dụng phát hiện các phần mã để chạy trong song song, tương đông. Giống như OpenMP, GCD cũng quản lý hầu hết các chi tiết của phân luồng. Nó xác định các phần mở rộng cho ngôn ngữ C và C ++ được gọi là các khối. Một khối chỉ đơn giản là một đơn vị công việc khép kín. Nó được chỉ định bởi một dấu mũ ˆ được chèn vào phía trước của một cặp dấu ngoặc nhọn {}. Dưới đây là một ví dụ đơn giản về khối -

{
   ˆprintf("This is a block");
}

Nó lên lịch cho các khối để thực thi thời gian chạy bằng cách đặt chúng vào hàng đợi gửi. Khi GCD loại bỏ một khối khỏi hàng đợi, nó sẽ gán khối cho một luồng có sẵn từ nhóm luồng mà nó quản lý. Nó xác định hai loại hàng đợi gửi:nối tiếp và đồng thời. Các khối được đặt trên một hàng đợi nối tiếp được loại bỏ theo thứ tự FIFO. Khi một khối đã được xóa khỏi hàng đợi, nó phải hoàn thành việc thực thi trước khi khối khác bị xóa. Mỗi tiến trình có hàng đợi nối tiếp riêng của nó (được gọi là hàng đợi chính). Nhà phát triển có thể tạo thêm hàng đợi nối tiếp cục bộ cho các quy trình cụ thể. Hàng đợi nối tiếp rất hữu ích để đảm bảo thực hiện tuần tự một số tác vụ. Các khối được đặt trên một hàng đợi đồng thời cũng bị xóa theo thứ tự FIFO, nhưng một số khối có thể bị xóa cùng một lúc, do đó cho phép nhiều khối thực thi song song. Có ba hàng đợi gửi đồng thời trên toàn hệ thống và chúng được phân biệt theo mức độ ưu tiên:thấp, mặc định và cao. Mức độ ưu tiên thể hiện ước tính về tầm quan trọng tương đối của các khối. Rất đơn giản, các khối có mức độ ưu tiên cao hơn nên được đặt trên hàng đợi gửi đi có mức độ ưu tiên cao. Đoạn mã sau minh họa việc lấy hàng đợi đồng thời có mức ưu tiên mặc định và gửi một khối đến hàng đợi bằng cách sử dụng hàm gửi async ():

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch async(queue, ˆ{ printf("This is a block."); });

Bên trong, nhóm luồng của GCD bao gồm các luồng POSIX. GCD chủ động quản lý nhóm, cho phép số lượng luồng phát triển và thu nhỏ theo nhu cầu ứng dụng và dung lượng hệ thống.

Chủ đề dưới dạng đối tượng

Trong các ngôn ngữ thay thế, các ngôn ngữ hướng đối tượng cổ đại cung cấp hỗ trợ đa luồng rõ ràng với các luồng là đối tượng. Trong các dạng ngôn ngữ này, vùng lớp được viết để mở rộng một lớp luồng hoặc triển khai một giao diện tương ứng. Phong cách này giống với cách tiếp cận Pthread, vì mã được viết với quản lý luồng rõ ràng. Tuy nhiên, việc đóng gói thông tin bên trong các lớp và các tùy chọn đồng bộ hóa bổ sung sẽ sửa đổi tác vụ.

Chuỗi Java

Java cung cấp một danh mục Chủ đề và một giao diện Runnable có thể được sử dụng. Mỗi cần triển khai một kỹ thuật public void run () xác định mục đích đầu vào của luồng. Khi một thể hiện của đối tượng được phân bổ, luồng sẽ được bắt đầu bằng cách gọi kỹ thuật start () trên đó. Giống như với Pthreads, bắt đầu luồng là không đồng bộ, sắp xếp theo thời gian của việc thực thi là không xác định.

Chuỗi Python

Python cũng cung cấp hai cơ chế cho đa luồng. Một cách tiếp cận có thể so sánh với kiểu Pthread, bất cứ nơi nào tên hàm được chuyển đến phương thức thư viện thread.start_new_thread (). Cách tiếp cận này rất nhiều và thiếu tính linh hoạt để tham gia hoặc kết thúc luồng khi nó bắt đầu. Một kỹ thuật linh hoạt bổ sung là sử dụng mô-đun phân luồng để phác thảo một lớp mở rộng phân luồng. Chủ đề. gần giống như cách tiếp cận Java, thể loại phải có một phương thức run () cung cấp mục đích nhập của luồng. Khi một đối tượng được khởi tạo từ danh mục này, nó có thể được bắt đầu và tham gia một cách rõ ràng sau đó.

Đồng thời khi thiết kế ngôn ngữ

Các ngôn ngữ lập trình mới hơn đã tránh được tình trạng chạy đua bằng cách xây dựng các giả định về thực thi đồng thời trực tiếp vào chính phong cách ngôn ngữ. Ví dụ, Go kết hợp một kỹ thuật phân luồng ngầm (goroutines) tầm thường với các kênh, một phong cách truyền thông điệp được xác định rõ ràng. Rust áp dụng một cách tiếp cận phân luồng xác định giống như Pthreads. Tuy nhiên, Rust có khả năng bảo vệ bộ nhớ cực kỳ mạnh mẽ mà kỹ sư phần mềm không cần phải làm thêm.

Goroutines

Ngôn ngữ Go bao gồm một cơ chế nhỏ để phân luồng ngầm:đặt từ khóa go trước một cuộc gọi. Luồng mới được chuyển một liên kết tới một kênh truyền thông báo. Sau đó, hầu hết các cuộc gọi chuỗi thành công:=<-messages, thực hiện quét nhiễu trên kênh. Khi người dùng đã nhập đúng bảy dự đoán, chuỗi của bộ kiểm tra bàn phím sẽ ghi vào kênh, cho phép nhiều chuỗi nhất tiến triển.

Các kênh và quy trình là thành phần cốt lõi của ngôn ngữ cờ vây, được thiết kế với niềm tin rằng hầu hết tất cả các chương trình sẽ được đa luồng. Phong cách thay thế này hợp lý hóa mô hình sự kiện, cho phép ngôn ngữ tự cập nhật trách nhiệm quản lý các luồng và lập trình.

Đồng tiền gỉ

Một ngôn ngữ khác là Rust đã được tạo ra trong những năm gần đây, với tính đồng thời là đặc điểm thiết kế trung tâm. Ví dụ sau minh họa việc sử dụng thread ::spawn () để tạo một chuỗi mới, sau này có thể được nối bằng cách gọi tham gia () trên đó. Đối số cho luồng ::spawn () bắt đầu tại || được gọi là một bao đóng, có thể được coi như một hàm ẩn danh. Tức là, chuỗi con ở đây sẽ in ra giá trị của a.

Ví dụ

use std::thread;
fn main() {
   /* Initialize a mutable variable a to 7 */
   let mut a = 7;
   /* Spawn a new thread */
   let child_thread = thread::spawn(move || {
      /* Make the thread sleep for one second, then print a */
      a -= 1;
      println!("a = {}", a)
   });
   /* Change a in the main thread and print it */
   a += 1;
   println!("a = {}", a);
   /* Join the thread and print a again */
   child_thread.join();
}

Tuy nhiên, có một điểm tinh tế trong mã này là trọng tâm trong thiết kế của Rust. Trong luồng mới (thực thi mã trong bao đóng), một biến khác với a trong các phần khác của mã này. Nó thực thi một mô hình bộ nhớ rất nghiêm ngặt (được gọi là "quyền sở hữu") ngăn nhiều luồng truy cập vào cùng một bộ nhớ. Trong ví dụ này, từ khóa di chuyển chỉ ra rằng luồng được tạo ra sẽ nhận được một bản sao riêng biệt của một để sử dụng riêng. Bất kể việc lập lịch của hai luồng, luồng chính và luồng con không thể can thiệp vào các sửa đổi của nhau đối với a, bởi vì chúng là các bản sao riêng biệt. Hai luồng không thể chia sẻ quyền truy cập vào cùng một bộ nhớ.