Clojure - Lập trình đồng thời

Trong lập trình Clojure, hầu hết các kiểu dữ liệu là bất biến, do đó khi nói đến lập trình đồng thời, mã sử dụng các kiểu dữ liệu này khá an toàn khi mã chạy trên nhiều bộ xử lý. Nhưng đôi khi, có yêu cầu chia sẻ dữ liệu và khi nói đến dữ liệu được chia sẻ trên nhiều bộ xử lý, điều cần thiết là phải đảm bảo rằng trạng thái của dữ liệu được duy trì về tính toàn vẹn khi làm việc với nhiều bộ xử lý. Điều này được gọi làconcurrent programming và Clojure cung cấp hỗ trợ cho việc lập trình như vậy.

Hệ thống bộ nhớ giao dịch phần mềm (STM), được hiển thị thông qua dosync, ref, set, alter, v.v. hỗ trợ chia sẻ trạng thái thay đổi giữa các luồng một cách đồng bộ và phối hợp. Hệ thống tác nhân hỗ trợ chia sẻ trạng thái thay đổi giữa các luồng một cách không đồng bộ và độc lập. Hệ thống nguyên tử hỗ trợ chia sẻ trạng thái thay đổi giữa các luồng một cách đồng bộ và độc lập. Trong khi hệ thống var động, được hiển thị thông qua def, binding, v.v. hỗ trợ cách ly trạng thái thay đổi trong các luồng.

Các ngôn ngữ lập trình khác cũng tuân theo mô hình để lập trình đồng thời.

  • Chúng có tham chiếu trực tiếp đến dữ liệu có thể được thay đổi.

  • Nếu quyền truy cập chia sẻ được yêu cầu, đối tượng sẽ bị khóa, giá trị bị thay đổi và quá trình tiếp tục cho lần truy cập tiếp theo vào giá trị đó.

Trong Clojure không có ổ khóa, nhưng tham chiếu gián tiếp đến cấu trúc dữ liệu liên tục bất biến.

Có ba loại tham chiếu trong Clojure.

  • Vars - Các thay đổi được cách ly trong chủ đề.

  • Refs - Các thay đổi được đồng bộ và phối hợp giữa các luồng.

  • Agents - Liên quan đến các thay đổi độc lập không đồng bộ giữa các luồng.

Các hoạt động sau đây có thể thực hiện được trong Clojure liên quan đến lập trình đồng thời.

Giao dịch

Đồng tiền trong Clojure dựa trên các giao dịch. Tham chiếu chỉ có thể được thay đổi trong một giao dịch. Các quy tắc sau được áp dụng trong giao dịch.

  • Tất cả các thay đổi là nguyên tử và cô lập.
  • Mọi thay đổi đối với tham chiếu đều xảy ra trong một giao dịch.
  • Không có giao dịch nào nhìn thấy hiệu ứng được thực hiện bởi một giao dịch khác.
  • Tất cả các giao dịch được đặt bên trong khối dosync.

Chúng ta đã thấy những gì khối dosync làm được, hãy xem lại nó.

đồng bộ hóa

Chạy biểu thức (trong một hành động ngầm định) trong một giao dịch bao gồm biểu thức và bất kỳ lệnh gọi lồng nhau nào. Bắt đầu giao dịch nếu không có giao dịch nào đang chạy trên chuỗi này. Bất kỳ trường hợp ngoại lệ không cần thiết nào cũng sẽ hủy bỏ giao dịch và chảy ra khỏi dosync.

Sau đây là cú pháp.

Cú pháp

(dosync expression)

Parameters - 'biểu thức' là tập hợp các biểu thức sẽ có trong khối dosync.

Return Value - Không.

Hãy xem một ví dụ trong đó chúng tôi cố gắng thay đổi giá trị của một biến tham chiếu.

Thí dụ

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   (alter names conj "Mark"))
(Example)

Đầu ra

Chương trình trên khi chạy sẽ báo lỗi như sau.

Caused by: java.lang.IllegalStateException: No transaction running
   at clojure.lang.LockingTransaction.getEx(LockingTransaction.java:208)
   at clojure.lang.Ref.alter(Ref.java:173)
   at clojure.core$alter.doInvoke(core.clj:1866)
   at clojure.lang.RestFn.invoke(RestFn.java:443)
   at clojure.examples.example$Example.invoke(main.clj:5)
   at clojure.examples.example$eval8.invoke(main.clj:7)
   at clojure.lang.Compiler.eval(Compiler.java:5424)
   ... 12 more

Từ lỗi, bạn có thể thấy rõ rằng bạn không thể thay đổi giá trị của loại tham chiếu mà không bắt đầu giao dịch trước.

Để đoạn mã trên hoạt động, chúng ta phải đặt lệnh thay đổi trong một khối dosync như được thực hiện trong chương trình sau.

Thí dụ

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   
   (defn change [newname]
      (dosync
         (alter names conj newname)))
   (change "John")
   (change "Mark")
   (println @names))
(Example)

Chương trình trên tạo ra kết quả sau.

Đầu ra

[John Mark]

Hãy xem một ví dụ khác về dosync.

Thí dụ

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def var1 (ref 10))
   (def var2 (ref 20))
   (println @var1 @var2)
   
   (defn change-value [var1 var2 newvalue]
      (dosync
         (alter var1 - newvalue)
         (alter var2 + newvalue)))
   (change-value var1 var2 20)
   (println @var1 @var2))
(Example)

Trong ví dụ trên, chúng ta có hai giá trị đang được thay đổi trong một khối dosync. Nếu giao dịch thành công, cả hai giá trị sẽ thay đổi, nếu không thì toàn bộ giao dịch sẽ thất bại.

Chương trình trên tạo ra kết quả sau.

Đầu ra

10 20
-10 40