Trong khi làm việc với tính khả dụng cao của các dịch vụ AI của chúng tôi, cả nhiều quy trình và luồng đều được sinh ra, chấm dứt và hồi sinh lại. Nếu một quá trình bị chấm dứt, các tài nguyên được phân bổ chỉ đơn giản là được giải phóng, nhưng với các luồng, quá trình này không đơn giản như vậy. Trong bài viết này, tôi sẽ giải thích cách tham chiếu yếu có thể thực sự hữu ích trong những tình huống này
Một phần dịch vụ của chúng tôi yêu cầu phản ứng với các sự kiện thời gian thực tích lũy trong hàng đợi có khả năng chịu lỗi. Để xử lý những sự kiện này, chúng tôi đã phát triển một ứng dụng tiêu dùng có tính sẵn sàng cao, đó là loại hàng đợi bất khả tri
Trình tiêu dùng hàng đợi của chúng tôi quản lý công nhân khởi động trong các luồng hoặc quy trình riêng lẻ, giám sát chúng, khởi động lại trong trường hợp chấm dứt và phát hiện các luồng hoặc quy trình bị treo
Tất cả những gì chúng ta cần cho một dự án là xác định trình xử lý, trình xử lý này sẽ được gọi khi sử dụng sự kiện. Đối với dự án này, giả sử trình xử lý truy xuất đường dẫn AWS s3 từ một sự kiện, tải tệp xuống và thực hiện một số nội dung 'AI' với nội dung của nó. Để làm việc với AWS, có một thư viện tuyệt vời tên là boto3
. Khi xử lý boto3
, AWS khuyến nghị ‘’
Điều này có nghĩa là, một mặt, chúng ta chỉ cần viết mã trình xử lý sự kiện và bỏ qua các luồng cũng như việc quản lý chúng, nhưng mặt khác, chúng ta nên tạo một phiên bản tài nguyên AWS khi tạo luồng [cụ thể là lệnh gọi trình xử lý đầu tiên] và sau đó giải phóng nó trên luồng.
Để tạo và xóa tài nguyên AWS trên mỗi lệnh gọi trình xử lý là không tối ưu vì mục tiêu của chúng tôi là bắt đầu và kết thúc luồng, đồng thời trong thời gian chạy luồng có thể xảy ra hàng nghìn lệnh gọi trình xử lý
Đó là lý do tại sao chúng tôi sử dụng Python weakref.WeakKeyDictionary
với một tinh chỉnh — chúng tôi đặt đối tượng luồng làm khóa và tài nguyên AWS làm giá trị. Do tính năng tham chiếu yếu, nếu các đối tượng chính [phiên bản luồng] bị xóa bởi GC [trình thu gom rác], thì tham chiếu đến giá trị [tài nguyên] cũng sẽ bị xóa và sau đó GC cũng có thể xóa nó [nếu không có tham chiếu nào khác đến . Hãy nhớ rằng Python GC sử dụng kỹ thuật đếm tham chiếu
Dưới đây là đoạn mã về cách chúng tôi gán tài nguyên s3 cho các luồng
Và khi chúng tôi sử dụng AWS SQS làm hàng đợi có khả năng chịu lỗi, chúng tôi vẫn tuân theo khuyến nghị của AWS ngoài việc duy trì nhóm tham chiếu yếu gồm các máy khách SQS
Người giám sát của khách hàng quan sát các luồng, loại bỏ các luồng chết và bắt đầu các luồng mới, điều này dẫn đến việc loại bỏ các đối tượng boto3
, liên quan đến các luồng chết khỏi nhóm
Điều gì về chủ đề an toàn? . Câu trả lời là cả có và không'. Sẽ không an toàn cho luồng nếu bạn cố lặp lại từ điển và một luồng đồng thời thêm phần tử mới vào đó, nhưng nếu các luồng chỉ thêm các mục thì không sao. weakref.WeakKeyDictionary
là trình bao bọc của Python trên từ điển tích hợp. Python đảm bảo các nguyên tử cho các phương thức trong các nguyên hàm tích hợp sẵn và việc thêm các mục vào từ điển cũng là nguyên tử. weakref.WeakKeyDictionary
không được tích hợp sẵn, đó là mã Python. Chúng tôi đã điều tra mã nguồn của nó và không tìm thấy lý do tại sao nó không an toàn cho luồng và chúng tôi cũng không gặp bất kỳ sự cố nào khi sử dụng. Chúng ta cũng nên xem xét mã nguồn weakref.WeakKeyDictionary
để đảm bảo rằng nó cũng an toàn cho luồng
mã để
def __setitem__[self, key, value]:
self.data[ref[key, self._remove]] = value
Như bạn thấy trong ví dụ, nó chỉ thêm một phần tử mới vào từ điển tích hợp vì
def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
1. Mặc dù vậy, có một mẹo trong đó nó bọc một khóa thành một tham chiếu yếu và đăng ký cuộc gọi lại def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
2, cuộc gọi này được gọi nếu khóa bị xóa bởi GC. Trong trường hợp của chúng tôi, điều này xảy ra khi chuỗi kết thúc và bị người giám sát xóa khỏi nhómHãy xem xét
def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
3 để đảm bảo an toàn cho luồng của nódef remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
Như bạn có thể thấy, hàm
def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
4 cũng thực hiện những việc nguyên tử [def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
5 theo mặc định]Bây giờ chúng ta đã hiểu cách gọi lại tham chiếu yếu được tạo và gọi trong
def remove[k, selfref=ref[self]]:
self = selfref[]
if self is not None:
if self._iterating:
self._pending_removals.append[k]
else:
del self.data[k]
self._remove = remove
6, chúng ta có thể mở rộng nó để thực hiện thêm những việc khác khi loại bỏ luồng. Hãy tưởng tượng rằng chúng ta cần khởi động một máy chủ khi tạo chuỗi và dừng nó khi kết thúc. Sau đó, một mã có thể được làm lại như thế nàyHãy cũng đảm bảo rằng nó có thể hoạt động. Chỉ cần thay đổi mã một chút để làm cho nó trực quan hơn
Và sau cuộc gọi
________số 8_______Nó hoạt động ngay cả khi luồng tăng ngoại lệ
kết quả cuộc gọi
$ python script.pyException: BOOM!
pool size is 1
before callback
callback is called
resource is deleted
after callback
pool size is 0
Tham chiếu yếu là một tính năng mạnh mẽ có thể được sử dụng ở những nơi khác nhau để tránh các vấn đề về quản lý bộ nhớ. Nếu bạn đã bắt đầu quan tâm đến nó, bạn cũng có thể xem cách tránh rò rỉ bộ nhớ với các tham chiếu vòng trong Python