线程安全的单例不起作用。 Python

Thread-safe Singleton doesn't work. Python

我试图找到此 中描述的错误,我找到了它。

它是 Singleton class,这是我从 Whosebug 得到的 post

当我开始测试 class 时,我发现那个程序崩溃了(就像第一个 link 中描述的那样)...

这是导致错误的最小示例:

Singleton.py

import threading


class Singleton(object):
    __singleton_lock = threading.Lock()
    __singleton_instance = None

    @classmethod
    def Instance(cls):
        if not cls.__singleton_instance:
            with cls.__singleton_lock:
                if not cls.__singleton_instance:
                    cls.__singleton_instance = cls()
        return cls.__singleton_instance

Data.py

#-*- coding: utf-8 -*-
from singletone import Singleton
import threading


class Data(Singleton):

    def __init__(self):
        self.task_table = {"Zadanie1.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie2.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie3.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie4.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie5.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie6.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie7.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie8.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie9.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie10.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie11.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie12.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie13.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie14.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie15.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie16.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie17.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie18.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie19.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie20.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie21.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie22.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie23.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie24.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie25.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie26.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie27.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie28.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie29.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie30.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie31.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie32.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie33.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie34.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie35.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie36.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie37.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie38.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie39.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie40.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie41.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie42.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie43.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie44.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie45.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie46.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie47.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie48.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie49.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie50.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie51.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie52.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie53.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie54.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie55.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie56.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie57.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie58.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie59.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie60.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie61.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie62.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie63.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie64.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie65.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie66.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie67.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie68.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie69.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie70.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie71.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie72.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie73.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie74.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie75.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie76.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie77.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie78.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie79.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie80.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie81.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie82.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie83.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie84.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie85.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie86.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie87.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie88.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie89.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie90.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie91.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie92.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie93.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie94.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie95.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie96.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie97.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie98.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie99.py": {'status': 'uncomplete', 'result': '', 'time': 0}, "Zadanie100.py": {'status': 'uncomplete', 'result': '', 'time': 0}}
        self.complete_task = threading.Event()
        self.complete_task.set()

    def getTotalCompleteTasks(self):
        result = 0
        for taskname in self.task_table.keys():
            if self.task_table[taskname]['status'] == 'complete':
                result += 1
        return result

Worker.py

#-*- coding: utf-8 -*-
import threading
import subprocess


class Worker(threading.Thread):

    def __init__(self, data, taskname):
        self.taskname = taskname
        self.data = data
        threading.Thread.__init__(self)

    def run(self):
        self.data.complete_task.clear()
        print u"Start executing %s" % self.taskname
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = subprocess.SW_HIDE
        p = subprocess.Popen("C:\Python27\python.exe tasks100.5\" + self.taskname, startupinfo=startupinfo, shell=False, stdout=subprocess.PIPE)
        job_result, err = p.communicate()
        print u"Stop executing %s" % self.taskname
        self.data.task_table[self.taskname]['status'] = 'complete'
        self.data.complete_task.set()

Agent.py

#-*- coding: utf-8 -*-
import communication_servers as cs
from worker import Worker
from data import Data
import time


app_data = Data.Instance()
SRV = cs.Server(app_data)
SRV.setDaemon(True)
SRV.start()

my_tasks = app_data.task_table.keys()[:]


while True:
    print u"Complete tasks: %d\n" % app_data.getTotalCompleteTasks()
    time.sleep(1)

    if my_tasks:
        if app_data.complete_task.is_set():
            job = Worker(app_data, my_tasks.pop())
            job.setDaemon(True)
            print u"Running"
            job.start()
        else:
            print u"Can't run"
        print u"Test string"

Communication_server.py

#-*- coding: utf-8 -*-
import threading
import time


class Server(threading.Thread):

    def __init__(self, data):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            print('Server still working...\n')
            time.sleep(5)

像 ZadanieXX.py 这样的所有文件都在那里:

import time
i = 2
p = i**400
time.sleep(5)
print p

真实代码只有缩减版。并且在 30 次启动中大约有 1 次导致错误。该错误是当 main while loop im Agent.py 不迭代或迭代时,但 complete_task 事件永远不会设置(在 100 次启动的情况下少于 1 次)。

p.s. I tried to use this Singleton pattern, but bug is still here...

任何想法,为什么它以这种方式工作?任何建议,我如何创建单实例、线程安全 class 来存储所有应用程序工作数据?

我的英语不是很好。对不起。

请注意,您对锁定的使用实际上不是线程安全的。在检查实例是否存在和锁定对该实例的访问之间存在竞争条件:

def Instance(cls):
    if not cls.__singleton_instance:
        # another thread may succeed with the previous check again here
        with cls.__singleton_lock:
            if not cls.__singleton_instance:
                cls.__singleton_instance = cls()
    return cls.__singleton_instance

你应该交换锁的获取和__singleton_instance的检查,因为前者保护后者。

请注意,在您的最小工作示例中,您正在显式构建单例,因此永远不会触发竞争条件(没有多线程实例化)。事实上,您的示例允许您在没有单身人士的情况下工作,因为您 显式 仅实例化您的 class 一次。

抱歉,这不是关于 Singleton DP 的答案,但我认为您没有理由必须使用 Singleton。

在你的示例中,Data class只是用作数据存储,你可以使用python自己的模块Queue进行单个数据存储。

8.10. Queue - A synchronized queue class

The Queue module implements multi-producer, multi-consumer queues. It is especially useful in threaded programming when information must be exchanged safely between multiple threads. The Queue class in this module implements all the required locking semantics. It depends on the availability of thread support in Python; see the threading module.

是的。它是线程安全的,因此您可以 Queue 作为单一的、集中的数据存储。

检查类似这样的东西。

#-*- coding: utf-8 -*-
from Queue import Queue
from threading import Thread
import time

# Set up some global variables
num_threads = 2
data_queue = Queue()

# Your Data
files = ['Zadanie1.py', 'Zadanie2.py', ... ]

def doSomething(i, q):
    while True:
        print '[{}] Waiting...'.format(i)
        filename = q.get()
        print '[{}] Working: {} '.format(i, filename)
        # Do something you need
        time.sleep(i + random.randint(1, 5))
        q.task_done()

# Set up threads
for i in range(num_threads):
    worker = Thread(target=doSomething, args=(i, data_queue,))
    worker.setDaemon(True)
    worker.start()
    # Note that you can start threads before filling queue,
    # because queue.get() will block until queue has data to return.

# Fill queue
for filename in files:
    print 'Queuing: {}'.format(filename)
    data_queue.put(filename)

# Wait for the queue to be empty.
data_queue.join()
print 'Done!'

结果会是这样

[0] Waiting...
[1] Waiting...
Queuing: Zadanie1.py
Queuing: Zadanie2.py
...
[1] Working: Zadanie1.py 
[0] Working: Zadanie2.py 
...
[0] Waiting...
[1] Waiting...
Done!

希望这对您有所帮助!