Lock – Chìa khóa giải quyết vấn đề Concurrency

Lock – Chìa khóa giải quyết vấn đề Concurrency

May 8, 2022 3 By Nam Vu

Bài viết trước mình có đề cập tới các vấn đề liên quan đến Concurrency, một trong những món khá khoai trong lập trình. Tuy nhiên tuy nhiều vấn đề là vậy nhưng không phải là không có bài giải.

Và ở bài viết này mình sẽ tiếp tục nói về chủ đề Concurrency với cách giải đầu tiên chính là Lock.

Bắt đầu thôi!

Concurrency Control chưa bao giờ là dễ dàng. Hãy bắt đầu phân tích từ tài nguyên nhé!

Tài nguyên trong phần mềm là một từ chỉ chung cho một đối tượng lưu trữ hay dữ liệu nào đó. Nó có thể là cơ sở dữ liệu database, file, thư mục, bộ nhớ đệm, ổ cứng, cloud, hay storage chẳng hạn. Nhưng suy cho cùng thì việc quản lý tài nguyên chỉ có 2 khâu chính đó là quản lý đọc và ghi. Vì bất cứ đối tượng lưu trữ nào thao tác với những thành phần khác trong phần mềm đều chỉ có 2 mục đích đọc và ghi mà thôi.

Quản lý ở đây chính là quản lý các hoạt động hay sự kiện đồng thời xảy ra trong cơ sở dữ liệu mà không làm xung đột lẫn nhau.

Quản lý tính đồng thời được sử dụng để giải quyết những xung đột chủ yếu xảy ra với hệ thống có nhiều người dùng cùng lúc. Nó đảm bảo rằng các giao dịch trong cơ sở dữ liệu được thực hiện đồng thời mà không vi phạm tính toàn vẹn dữ liệu của cơ sở dữ liệu khác tương ứng. 

Nếu tất cả người dùng cùng đang đọc một dữ liệu thì việc kiểm soát sẽ được thực hiện một cách dễ dàng để đảm bảo không có trường hợp các người dùng này có thể can thiệp lên việc của nhau. 

Đối với bất kỳ tài nguyên nào cũng sẽ có sự kết hợp giữa các thao tác đọc và ghi dữ liệu, do đó điều này sẽ gây khó khăn hơn cho việc đảm bảo quản lý tính đồng thời.

Kiểm soát tính đồng thời là một yếu tố quan trọng đối với việc hoạt động bình thường của một hệ thống khi có hai hoặc nhiều giao dịch yêu cầu quyền truy cập vào cùng một cơ sở dữ liệu cùng một lúc.

Ví dụ để giảm tải ùn tắc giao thông thì cần mở rộng đường, phân làn xe và độ ưu tiên. Hay việc chia thành nhiều quầy thu ngân để việc thanh toán mua hàng trong siêu thị được nhanh hơn. Đó là những ví dụ cho việc quản lý tính đồng thời.

Và để quản lý tính đồng thời này thì có một từ khóa mà bạn cần phải tìm hiểu tiếp đó chính là Lock. Lock-Based Protocols chỉ là một trong số cách cách giải bài toán Concurrency thôi nhé, nó không phải là duy nhất, bên cạnh nó còn Two Phase, Timestamp-Based Protocols, Validation-Based Protocols.

Lock là một biến dữ liệu được liên kết với một phần tử dữ liệu. Nó sẽ thông báo rằng các thao tác có thể được thực hiện trên phần tử dữ liệu. Lock giúp đồng bộ hóa quyền truy cập vào các phần tử dữ liệu trong cơ sở tài nguyên. Tất cả các yêu cầu với lock được đưa tới trình kiểm soát tính đồng thời. Các giao dịch (transactions) chỉ được tiến hành sau khi yêu cầu với được cấp phép.

Bạn hiểu đơn giản Lock như một anh công an giao thông phân luồng khi tắc đường vậy. Anh ấy bắt luồng bên trái dừng thì dừng, luồng bên phải dừng thì dừng. Ở đây sẽ có một từ khóa nữa chính là Semaphore (mình sẽ có một bài viết khác nữa nói riêng về phần này)

Có các kiểu cơ chế lock như sau:

Binary locks: Khóa nhị phân được thực hiện trên một phần tử dữ liệu có thể ở trạng thái bị khóa hoặc mở khóa. Kiểu khóa này như một công tắc đóng mở để đọc ghi dữ liệu mà thôi.

– Shared/exclusive lock: Loại cơ chế khóa này sẽ phân biệt các khóa dựa trên mục đích sử dụng của các khóa đó.

Cụ thể hơn, Shared lock còn được gọi là khóa chỉ thực hiện việc đọc. Với kiểu khóa này, phần tử dữ liệu trong cơ sở tài nguyên có thể được dùng chung giữa các transactions. Điều này sẽ khiến người dùng không bao giờ có quyền cập nhật hay thay đổi dữ liệu này. 

Ví dụ bạn muốn đọc số tiền trong tài khoản ngân hàng của bạn ở nhiều nơi khác nhau cùng một lúc thì cơ chế của hệ thống có thể chia sẻ quyền đọc cho các yêu cầu gửi đến. Vì nó không làm thay đổi dữ liệu mà chỉ mang tính chất xem dữ liệu nên có thể dùng chung.

Với Exclusive lock, một phần tử dữ liệu có thể được đọc hoặc ghi. Các giao dịch có thể mở khóa cho phần tử dữ liệu sau khi kết thúc một thao tác đọc hoặc ghi.

Vẫn ví dụ trên, nếu bạn muốn rút tiền hoặc nạp thêm tiền vào tài khoản ngân hàng của bạn ở nhiều nơi cùng một lúc. Thì lúc này bạn sẽ phải xử lý biến những giao dịch đồng thời kia thành có thứ tự nhằm mục đích đảm bảo tính toàn vẹn dữ liệu và có thể tính toán số tiền trong tài khoản một cách chính xác. Các giao dịch phía sau mặc dù đồng thời đó nhưng vẫn phải đợi tới lượt thì mới có thể cập nhập dữ liệu. Nói vậy thôi chứ quá trình này diễn ra rất nhanh nên thực tế bạn vẫn cảm thấy nó là đồng thời cùng một lúc.

– Simplistic lock: Kiểu khóa này cho phép các giao dịch áp dụng khóa trên mỗi dữ liệu trước khi bắt đầu một thao tác. Các giao dịch có thể mở khóa cho phần tử dữ liệu sau khi kết thúc thao tác ghi hoặc đọc. Hãy tưởng tượng thay vì phân luồng đóng mở theo mục đích đọc ghi thì simplistic lock chia nhỏ trên từng dữ liệu để kiểm soát đọc ghi một cách dễ dàng hơn.

Ví dụ như thay vì đèn xe giao thông cho cả con đường đi hay dừng thì, chia thành nhiều cột đèn giao thông trên từng làn xe riêng biệt. Rẽ trái, rẽ phải, đi thẳng, hay thậm chí là từng làn cho ô tô, xe máy riêng biệt sẽ khiến cho luồng này không phải chờ đợi các luồng khác để được di chuyển.

– Pre-claiming lock: Kiểu khóa này giúp đánh giá các thao tác và tạo danh sách cho các mục dữ liệu cần được thực thi. Trong tình huống khi tất cả các khóa được cấp phép, giao dịch sẽ được thực hiện. Sau đó, tất cả các khóa sẽ được bỏ sau khi tất cả các thao tác kết thúc.

Ví dụ cho kiểu khóa này sẽ là những biển báo cột phân làn giao thông tạm thời để có thể điều chuyển và quản lý được các luồng di chuyển đi hay dừng. Sau khi được sử dụng thì nó sẽ được gỡ bỏ. Tuy nhiên quá trình dùng kiểu khóa này phải chú ý đến thời gian gỡ bỏ (Starvation) hay nguy cơ tắc nghẽn (Deadlock)

– Pessimistic lock: Kiểu khóa cho phép giao dịch sau được thông qua dựa vào giao dịch trước. Nó sẽ luôn đảm bảo hai transactions sẽ không bao giờ có thể cùng thay đổi một phần tử tài nguyên. Nói đến kiểu khóa này bạn có thể tìm hiểu thêm từ khóa hàng đợi (queue) và mình sẽ đề cập nó ở một bài viết khác.

Ví dụ cho kiểu khóa này sẽ là kiểu khóa chặn ở những trạm thu phí giao thông. Chỉ khi xe trước hoàn tất quá trình thu phí và di chuyển thì xe sau mới bắt đầu vào để thực hiện được.

– Optimistic lock: Vấn đề khác xảy ra với kiểu pessimistic lock đó chính là thời gian chờ đợi những giao dịch phía sau phụ thuộc hết vào giao dịch trước. Và Optimistic lock sinh ra để cải thiện vấn đề đó. Nó cho phép nhiều giao dịch có thể hoàn thành mà không ảnh hưởng đến nhau. Trường hợp sử dụng nhiều với kiểu khóa này đó chính là cập nhật cơ sở dữ liệu trên nhiều transaction khác nhau, nó diễn ra độc lập, và chỉ khóa trong quá trình commit đẩy dữ liệu lưu lại mà thôi.

Ví dụ cho kiểu khóa này đó là mở cửa quá trình đăng ký và nạp tiền sẵn cho mọi xe đi qua trạm thu phí từ trước. Quá trình này độc lập xe nào tự đăng ký xe đó. Đến khi qua trạm thu phí không dừng thì chỉ cần kiểm tra và tự động trừ tiền vào tài khoản mà thôi. Khi đó xe sẽ được di chuyển nhanh hơn mà không phải tốn quá nhiều thời gian chờ đợi thanh toán và xé phiếu quẹt thẻ.

Trên đây là các loại và ví dụ về Lock giúp giải quyết và quản lý tính đồng thời. Hi vọng bài viết có thể giúp bạn hiểu được sự khó khăn trong quá trình thực hiện khi làm việc với concurrency.

#ntechdevelopers