Sử dụng một biến toàn cục với một chuỗi


84

Làm cách nào để chia sẻ biến toàn cục với luồng?

Ví dụ mã Python của tôi là:

from threading import Thread
import time
a = 0  #global variable

def thread1(threadname):
    #read variable "a" modify by thread 2

def thread2(threadname):
    while 1:
        a += 1
        time.sleep(1)

thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )

thread1.join()
thread2.join()

Tôi không biết làm thế nào để có được hai chủ đề để chia sẻ một biến.

Câu trả lời:


97

Bạn chỉ cần khai báo alà global in thread2, để bạn không sửa đổi một alocal cho hàm đó.

def thread2(threadname):
    global a
    while True:
        a += 1
        time.sleep(1)

Trong đó thread1, bạn không cần phải làm bất cứ điều gì đặc biệt, miễn là bạn không cố gắng sửa đổi giá trị của a(điều này sẽ tạo ra một biến cục bộ làm bóng biến toàn cục; sử dụng global anếu bạn cần)>

def thread1(threadname):
    #global a       # Optional if you treat a as read-only
    while a < 10:
        print a

47

Trong một hàm:

a += 1

sẽ được trình biên dịch giải thích là assign to a => Create local variable a, đó không phải là điều bạn muốn. Nó có thể sẽ không thành công với một a not initializedlỗi vì (cục bộ) a thực sự chưa được khởi tạo:

>>> a = 1
>>> def f():
...     a += 1
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

Bạn có thể đạt được những gì bạn muốn với từ khóa (rất khó chịu và vì lý do chính đáng) global, như vậy:

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

Tuy nhiên, nói chung, bạn nên tránh sử dụng các biến toàn cục trở nên quá nhanh chóng. Và điều này đặc biệt đúng với các chương trình đa luồng, nơi bạn không có bất kỳ cơ chế đồng bộ hóa nào thread1để biết khi nào ađã được sửa đổi. Tóm lại: các luồng rất phức tạp và bạn không thể mong đợi có được sự hiểu biết trực quan về thứ tự các sự kiện đang xảy ra khi hai (hoặc nhiều) luồng hoạt động trên cùng một giá trị. Ngôn ngữ, trình biên dịch, hệ điều hành, bộ xử lý ... TẤT CẢ đều có thể đóng một vai trò nào đó và quyết định thay đổi thứ tự hoạt động vì tốc độ, tính thực tế hoặc bất kỳ lý do nào khác.

Cách thích hợp cho loại điều này là sử dụng các công cụ chia sẻ Python ( khóa và bạn bè), hoặc tốt hơn, giao tiếp dữ liệu qua Hàng đợi thay vì chia sẻ nó, ví dụ như thế này:

from threading import Thread
from queue import Queue
import time

def thread1(threadname, q):
    #read variable "a" modify by thread 2
    while True:
        a = q.get()
        if a is None: return # Poison pill
        print a

def thread2(threadname, q):
    a = 0
    for _ in xrange(10):
        a += 1
        q.put(a)
        time.sleep(1)
    q.put(None) # Poison pill

queue = Queue()
thread1 = Thread( target=thread1, args=("Thread-1", queue) )
thread2 = Thread( target=thread2, args=("Thread-2", queue) )

thread1.start()
thread2.start()
thread1.join()
thread2.join()

Điều này giải quyết một vấn đề lớn. Và có vẻ như khá nhiều cách tiếp cận đúng để thực hiện nó.
Abhidemon

Đây là cách tôi đang sử dụng để giải quyết vấn đề đồng bộ hóa.
Zhang LongQI

1
Tôi có một số câu hỏi: Đầu tiên, Nếu tôi có nhiều biến để chia sẻ giữa các luồng, tôi có cần một hàng đợi riêng cho từng biến không? Thứ hai, tại sao các hàng đợi trong chương trình trên lại được đồng bộ hóa? Mỗi chức năng không nên hoạt động như một bản sao cục bộ?

Điều này là cũ, nhưng tôi trả lời dù sao. Bản thân hàng đợi không được đồng bộ hóa, không nhiều hơn biến a. Đó là hành vi chặn mặc định của hàng đợi tạo ra sự đồng bộ hóa. Câu lệnh a = q.get()sẽ chặn (đợi) cho đến khi có giá trị a. Biến qlà cục bộ: nếu bạn gán một giá trị khác cho nó, nó sẽ chỉ xảy ra cục bộ. Nhưng hàng đợi được gán cho nó trong mã là hàng được xác định trong luồng chính.

1
Không phải lúc nào cũng cần sử dụng hàng đợi để chia sẻ thông tin giữa các luồng. Ví dụ trong câu trả lời chepner là hoàn toàn tốt. Ngoài ra, hàng đợi không phải lúc nào cũng là công cụ phù hợp. Ví dụ: hàng đợi rất hữu ích nếu bạn muốn chặn cho đến khi giá trị có sẵn. Sẽ vô ích nếu hai luồng cạnh tranh trên một tài nguyên được chia sẻ. Cuối cùng, các biến toàn cục không tệ nhất trong các luồng. Trên thực tế, chúng có thể tự nhiên hơn. Ví dụ: chuỗi của bạn có thể chỉ là một khối mã, chẳng hạn như một vòng lặp, cần quy trình riêng của nó. Phạm vi cục bộ do đó được tạo một cách giả tạo khi bạn đặt vòng lặp vào một hàm.

5

Một khóa nên được xem xét để sử dụng, chẳng hạn như threading.Lock. Xem các đối tượng khóa để biết thêm thông tin.

Câu trả lời được chấp nhận CÓ THỂ in 10 theo thread1, đây không phải là điều bạn muốn. Bạn có thể chạy đoạn mã sau để hiểu lỗi dễ dàng hơn.

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print "unreachable."

def thread2(threadname):
    global a
    while True:
        a += 1

Sử dụng khóa có thể cấm thay đổi atrong khi đọc nhiều hơn một lần:

def thread1(threadname):
    while True:
      lock_a.acquire()
      if a % 2 and not a % 2:
          print "unreachable."
      lock_a.release()

def thread2(threadname):
    global a
    while True:
        lock_a.acquire()
        a += 1
        lock_a.release()

Nếu luồng sử dụng biến trong thời gian dài, thì việc xử lý nó với biến cục bộ trước là một lựa chọn tốt.


3

Cảm ơn rất nhiều Jason Pan đã đề xuất phương pháp đó. Câu lệnh if của thread1 không phải là nguyên tử, do đó trong khi câu lệnh đó thực thi, thread2 có thể xâm nhập vào thread1, cho phép đạt được mã không thể truy cập được. Tôi đã sắp xếp các ý tưởng từ các bài viết trước thành một chương trình trình diễn hoàn chỉnh (bên dưới) mà tôi đã chạy với Python 2.7.

Với một số phân tích chu đáo, tôi chắc chắn rằng chúng ta có thể hiểu sâu hơn, nhưng hiện tại tôi nghĩ điều quan trọng là phải chứng minh điều gì sẽ xảy ra khi hành vi phi nguyên tử gặp luồng.

# ThreadTest01.py - Demonstrates that if non-atomic actions on
# global variables are protected, task can intrude on each other.
from threading import Thread
import time

# global variable
a = 0; NN = 100

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print("unreachable.")
    # end of thread1

def thread2(threadname):
    global a
    for _ in range(NN):
        a += 1
        time.sleep(0.1)
    # end of thread2

thread1 = Thread(target=thread1, args=("Thread1",))
thread2 = Thread(target=thread2, args=("Thread2",))

thread1.start()
thread2.start()

thread2.join()
# end of ThreadTest01.py

Theo dự đoán, khi chạy ví dụ, đôi khi mã "không thể truy cập" thực sự đạt đến, tạo ra đầu ra.

Chỉ cần nói thêm, khi tôi chèn một cặp thu thập / phát hành khóa vào luồng1, tôi thấy rằng xác suất có bản in thông báo "không thể truy cập" đã giảm đáng kể. Để xem thông báo, tôi giảm thời gian ngủ xuống 0,01 giây và tăng NN lên 1000.

Với một cặp thu thập / phát hành khóa trong luồng1, tôi không mong đợi thấy thông báo nào cả, nhưng nó ở đó. Sau khi tôi chèn một cặp thu thập / phát hành khóa vào thread2, thông báo không còn xuất hiện nữa. Trong dấu hiệu sau, câu lệnh tăng trong thread2 có thể cũng không phải là nguyên tử.


1
Bạn cần các khóa trong cả hai chủ đề vì đây là các "khóa tư vấn" hợp tác (không phải "bắt buộc"). Bạn đúng ở chỗ câu lệnh tăng không phải là nguyên tử.
Darkonaut

1

Vâng, ví dụ đang chạy:

CẢNH BÁO! KHÔNG BAO GIỜ LÀM VIỆC NÀY TẠI NHÀ / CÔNG VIỆC! Chỉ trong lớp học;)

Sử dụng các semaphores, các biến chia sẻ, v.v. để tránh các điều kiện vội vàng.

from threading import Thread
import time

a = 0  # global variable


def thread1(threadname):
    global a
    for k in range(100):
        print("{} {}".format(threadname, a))
        time.sleep(0.1)
        if k == 5:
            a += 100


def thread2(threadname):
    global a
    for k in range(10):
        a += 1
        time.sleep(0.2)


thread1 = Thread(target=thread1, args=("Thread-1",))
thread2 = Thread(target=thread2, args=("Thread-2",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

và đầu ra:

Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 2
Thread-1 3
Thread-1 3
Thread-1 104
Thread-1 104
Thread-1 105
Thread-1 105
Thread-1 106
Thread-1 106
Thread-1 107
Thread-1 107
Thread-1 108
Thread-1 108
Thread-1 109
Thread-1 109
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110

Nếu đúng thời gian, a += 100thao tác sẽ bị bỏ qua:

Processor thực hiện tại T a+100và nhận được 104. Nhưng nó dừng lại và chuyển sang luồng tiếp theo Tại đây, Tại T + 1 thực thi a+1với giá trị cũ là a , a == 4. Vì vậy, nó tính 5. Quay lại (tại T + 2), luồng 1, và ghi a=104vào bộ nhớ. Bây giờ quay lại luồng 2, thời gian là T + 3 và ghi a=5vào bộ nhớ. Thì đấy! Hướng dẫn in tiếp theo sẽ in 5 thay vì 104.

RẤT khó chịu để được tái tạo và bắt lỗi.


Vui lòng xem xét thêm một cách triển khai đúng. Điều đó sẽ rất hữu ích cho những người học cách chia sẻ dữ liệu giữa các luồng.
JS.

1
Thêm vào danh sách "todo" :)
VISOFT
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.