Python:如何在所有线程完成任务之前锁定对象

时间:2018-03-18 04:55:30

标签: python multithreading sockets

我正在编写一个简单的线程服务器,它将向所有客户端发送消息。我有一个在发布更改消息后重置的对象,但是我很难确定如何在所有线程都发布更改消息后重置该对象。

为问题添加一些上下文。我正在构建一个多用户Tkinter python应用程序,它连接到远程数据库以检索信息,应用程序需要知道数据何时更改,以便当用户更新数据时,应用程序的所有其他运行实例都将获得更新。据我所知,MySQL不支持异步应用程序更新。我没有在数据库上每隔5秒运行一次查询以查看是否有更改,而是将此代码服务器放在一边,以便它向客户端上的套接字发送消息,表明数据库发生了更改。

主循环只是一个模拟变化的虚拟

这是我的代码:

import socket, threading, time, select, os

class dbMonitor:
   isDBAltered = False
   def postChange(self):
       self.isDBAltered = True
   def __str__(self):
        return str(self.isDBAltered)


class ThreadedServer(object):
    def __init__(self, port,dbMon):
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.setblocking(0)
        self.sock.bind((socket.gethostname(), self.port))
        self.dbMon = dbMon


    def listen(self):
        self.sock.listen(100)
        read_list = [self.sock]
        while True:
            read,write,error = select.select(read_list,[],[],1)
            for s in read:
                if s is self.sock:
                    client, address = self.sock.accept()
                    client.settimeout(60)
                    threading.Thread(target = self.listenToClient, args = (client,address)).start()
    def listenToClient(self, client, address):
        read_list = [client]
        size = 1024
        while True:
            response = b'Ack'
            if self.dbMon.isDBAltered:
                response = b'CHANGE'
                try:
                    client.send(response)
                except:
                    client.close()
                    return False
                self.dbMon.isDBAltered = False

            read,write,error = select.select(read_list,[],[],1)
            for s in read:
                if s is client:
                    try:
                        data = client.recv(size)
                        print(data)
                        if data:

                             client.send(response)
                        else:
                            raise error('Client disconnected')
                    except:
                        client.close()
                        return False

def mainLoop():

    while True:
        time.sleep(15)
        print(dbMon)
        dbMon.postChange()

dbMon = dbMonitor()
server = ThreadedServer(5005,dbMon)
threading.Thread(target = mainLoop, args=()).start()
threading.Thread(target = server.listen(), args=()).start()

如何在所有线程执行完毕后才能执行self.dbMon.isDBAltered = False

response = b'CHANGE'
                try:
                    client.send(response)

1 个答案:

答案 0 :(得分:0)

你正试图同步异步的东西......这比应该的要复杂得多。你的dbmon只存储一个布尔标志...为什么不只是异步修改“数据库”呢?例如,如果“数据库”是一个线程安全缓冲区,您可以只是附加到该缓冲区或修改该缓冲区而不单独同步每个线程,将写入该缓冲区的信息写入它们所属的客户端套接字。另一个事件循环(这几乎是asyncore所做的)

那就是说,我有一些(可能是非工作,但我希望你能得到这个想法)参考修改后的代码,如果你想继续追求这条道路,你就可以了。

基本上,dbmon将保持线程ID到[创建时间,修改标志]

的映射

如果在某个阈值之前创建的所有线程都设置了修改后的标志,则我们的谓词返回true。当我们在代码的data = client.recv(size)部分发送响应时,我们设置了修改标志。然后我们在服务器发送中等待那个条件。我们不断通知每个客户端接收的所有等待线程,以便在最终满足条件时,我们的等待服务器线程将全部解除阻塞并发送后续响应。

import socket, threading, time, select, os
import collections

class dbMonitor:
   def __init__(self):
       self.isDBAltered = {}
       self.lock = threading.Lock()
   def newThread(self, tid):
       self.lock.acquire()
       # time of creation, boolean whether that thread has sent response
       self.isDBAltered[tid] = [time.time(), False] 
       self.lock.release()
   def threadDone(self, tid):
        self.lock.acquire()
        self.isDBAltered.pop(tid, None)
        self.lock.release()
    def altered(self, tid):
        self.lock.acquire()
        self.isDBAltered[tid][1] = True
        self.lock.release()
    def reset(self, tid):
        self.lock.acquire()
        self.isDBAltered(tid)[1] = False
        self.lock.release()
   def __str__(self):
        return str(self.isDBAltered)


class ThreadedServer(object):
    def __init__(self, port,dbMon):
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.setblocking(0)
        self.sock.bind((socket.gethostname(), self.port))
        self.dbMon = dbMon
        self.lock = threading.lock()
        self.cv = threading.Condition()
        self.thresh = 2000

    def condition_pred(self):
        # unblock if threads currently running for longer than self.thresh have all set their flags
        return all([timecreate[1] if time.time() - timecreate[0] > self.thresh else True for tid,timecreate in self.dbMon.isDBAltered])

    def listen(self):
        self.sock.listen(100)
        read_list = [self.sock]
        while True:
            read,write,error = select.select(read_list,[],[],1)
            for s in read:
                if s is self.sock:
                    self.lock.acquire()
                    client, address = self.sock.accept()
                    client.settimeout(60)
                    T = threading.Thread(target = self.listenToClient, args = (client,address)).start()
                    self.dbmon.newThread(T.ident)
                    self.lock.release()
    def listenToClient(self, client, address):
        read_list = [client]
        size = 1024
        while True:
            response = b'Ack'
            with self.cv:
                self.cv.wait_for(self.condition_pred)
                self.dbMon.reset(threading.get_ident())
                response = b'CHANGE'
                try:
                    client.send(response)
                except:
                    client.close()
                    self.dbmon.threadDone(threading.get_ident())
                    return False

            read,write,error = select.select(read_list,[],[],1)
            for s in read:
                if s is client:
                    with self.cv:
                        try:
                            data = client.recv(size)
                            print(data)
                            if data:
                                 client.send(response)
                                 self.dbMon.altered(threading.get_ident())
                                 self.cv.notifyAll()
                            else:
                                raise error('Client disconnected')
                        except:
                            client.close()
                            self.dbmon.threadDone(threading.get_ident())
                            return False