views:

68

answers:

3

Hello guys, thank your for reading.

Well, I have a single python process which is using a serial port (unique resource) which is manage using an instance of a (A=Class A). Exist two different thread initialized using B=Class_B() and C=Class_C(), which are constantly using the serial port resource through the objected already created called.

Class A(threading.Thread):
    #Zigbee serial port handler
    def __init__(self,dev):
        #something here that initialize serial port
    def run():
        while True:
            #listening serial interface
    def pack(self):
        #something
    def checksum(self):
        #something
    def write(self):
        #something

Class B(threading.Thread):
    def __init__(self,SerialPortHandler):
        self.serialporthandler=SerialPortHandler
    def run(self)
        while True:
            #something that uses self.serialporthandler

Class C(threading.Thread):
    def __init__(self,SerialPortHandler):
        self.serialporthandler=SerialPortHandler
    def run(self)
        while True:
            #something that uses self.serialporthandler


def main():
    a=A('/dev/ttyUSB1')
    b=B(a)
    b.start()
    c=C(a)
    c.start()

if __name__=='main':
    while True:
        main()

The problem is obvious, and is that both thread are trying to access the serial resource at the same time. I could use several instances of the same Class A, attaching Lock.acquire() and Lock.release() in the sensitive parts, but the

Could some of you point me to the right way?

thank you in advance

A: 

Add a threading.Lock() to class A and make it acquire the lock when using it:

def __init__(self,dev):
    self.lock = threading.Lock()

def read(self):
    self.lock.acquire()
    data = ? #whatever you need to do here to read
    self.lock.release()
    return data

def write(self, data):
    self.lock.acquire()
    #whatever you need to do to write
    self.lock.release()
Locks are good, it's just that in Python you use them like `with self.lock: return something()`
THC4k
Assuming OP is using 2.6 or later.
Beware of exceptions being thrown while you're holding the lock. Using `with` (Python 2.6+) here or `try/finally` would be prudent.
Eli Bendersky
@user470379: i should also do -1 for you is that it ?? because if i follow your SO philosophy if i don't agree with a solution i should do -1 !!! just to make this clear the -1 is when you found a wrong answer for a question ; you see we're here to help the OP and we aren't doing a competition, so please be more respectful to others choices thanks :)
singularity
@singularity I didn't mean to cause any offense to you, but I felt that your answer still wasn't thread safe, which can be a very tricky thing to get right. If you feel my answer is incorrect, then by all means, __please__ -1 and propose a correction. Thanks :)
@user470379: like i said your answer is correct and mine too, you have just to understand it before doing a -1 that all, the difference between our two answers is that you use a lock while reading and for me i just use the serial.flush(), so basically my solution when a read() is called by a thread this thread will wait until all data have been written before reading (check Pyserial); in the other hand your solution you have to acquire a lock if you want to read, and i will tell that with this solution you will have a lot of collision.
singularity
A: 

you could start with something like this:

Class A():
    #Zigbee serial port handler
    def __init__(self,dev):
        self.lock = = threading.Lock()
        #something here that initialize serial port
    def read(self):
        # use flush to wait until all data has been written
        # if you are using pyserial
        self.serial.flush()  
        #something
    def write(self):
        self.lock.acquire()
        #something
        self.lock.release()

Class B(threading.Thread):
    def __init__(self,SerialPortHandler):
        self.serialporthandler=SerialPortHandler
    def run(self)
        while True:
            #something that uses self.serialporthandler

Problem that you can face :

  • read() can return false data example: when you are reading from the serial port and another tread have already started writing , you will get only old data; unless you do a flush() before reading so that it will wait until all data is written.

  • your biggest problem is when writing data to the serial port ; Locking is necessaries here, but maybe you can need to use Rlock() rather.

  • if you want nonblocking operation you can use select() (it's not advices if you use pySerial) or self.lock.acquire(False)

singularity
-1: As you said, you risk getting the same data from the serial port twice in two different threads, and it's not stated whether simultaneous read/write access is allowed? Why not just add a lock for read as well, whether it's a separate read lock, or the same lock?
like i said in my answer __is something to start with__ , i don't know all the possible case, and how he will use the serial port neither do you ; so basically it's __something to start with__ ; about your solution of using a lock in the read(); __first__ of all creating a new lock, why in the hell i want to do that (what's the problem of 2+ thread that read from the same serial port !!!!) why locking the read() ???? and __two__ using the same lock; if in his case he do a lot of read and write the number of collision will be much bigger so ..., __can you detail more why not flush() ??__
singularity
First, as your code stands right now, it's perfectly possible that a `read` from `B` could attempt to `flush` the buffer while in the middle of a `write` from `C`... then that `write` could write more in between the `read` flush and actually reading, so I'm not sure what purpose the `flush` is accomplishing, especially while trying to `read`? Second, with no further comments from OP, thread safety of `read` should not be assumed. (If buffered, a thread could retrieve data from the buffer after another thread retrieved that same data but before that thread emptied the buffer)
yes sorry if i didn't detailed more the answer , but the flush is not what you think it's, i was based on the Pyserial library which deal very well with this kind of problem , here is a quote from the documentation __flush(): Flush of file like objects. In this case, wait until all data is written.__ , and i think that this is more correct than using another lock on the read(), because in all I/O bound threads the problems (condition race, ...) is not while reading data , but when writing, and in this case we want also to be able to retrieve newest data, that what the flush() is for .
singularity
+5  A: 

While you could share the serial port using appropriate locking, I wouldn't recommend it. I've written several multi-threaded applications that communicate on the serial port in Python, and in my experience the following approach is better:

  • Have a single class, in a single thread, manage the actual serial port communication, via a Queue object or two:
    • Stuff read from the port is placed into the queue
    • Commands to send to the port are placed into the queue and the "Serial thread" sends them
  • Have the other threads implement logic by placing things into the queue and taking things out

Using Queue objects will greatly simplify your code and make it more robust.

This approach opens a lot of possibilities for you in terms of design. You can, for example, register events (callbacks) with the serial thread manager and have it call them (in a synchronized way) when interesting events occur, etc.

Eli Bendersky