Search code examples
pythonmultithreadingstdin

how do i keep closing a subthread from closing the stdin file descriptor?


I wrote this bit of code to clarify my issue... I keep getting ValueError: I/O operation on closed file.

None of the child threads read from stdin. The loop works great until I start a child thread... can someone tell me how to keep the file descriptor from closing?

import threading
from threadtest2 import Threadtest
import termios, sys, tty
import time

def getchar():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch


tt2 = Threadtest()
stop = threading.Event()

t1 = threading.Thread(target=tt2.thread1, args=[stop, ])
t2 = threading.Thread(target=tt2.thread2, args=[stop, ])

try:
    while 1:
        while not stop.isSet():
            try:
                c = getchar()
            except IOError: pass
            if c == "q":
                stop.set()
            if c == "x":
                stop.set()
                exit()
            if c == "1":
                print "starting t1"
                t1.start()
            if c == "2":
                print "starting t2"
                t2.start()
        while len(threading.enumerate()) > 1:
            print 'waiting for ' + str(len(threading.enumerate()) - 1) + ' threads to close\r'
            time.sleep(1)
        stop.clear()
        print "stop has been triggered and reset... restart"
finally:
    print "done!"

there have been a few other threads (pardon the pun) that touched on this, but I haven't found one that directly addresses it and have been flailing for a while.

FYI, the child threads just wait for stop to be set and sleep...


Solution

  • I made small changes to your code to run it standalone. The following does not generate the error for me on a Linux machine. Do you still see the error with it? If so, I'd be glad to improve the answer - please give a few more details about how you are running the code such the operating system in use.

    import threading
    import termios, sys, tty
    import time
    
    def getchar():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch
    
    class Threadtest:
        def thread1(self, stop):
            stop.wait()
            print "returning from thread1"
        def thread2(self, stop):
            stop.wait()
            print "returning from thread2"
    
    tt2 = Threadtest()
    stop = threading.Event()
    
    try:
        while 1:
            t1 = threading.Thread(target=tt2.thread1, args=[stop, ])
            t2 = threading.Thread(target=tt2.thread2, args=[stop, ])
    
            while not stop.isSet():
                try:
                    c = getchar()
                except IOError: pass
                if c == "q":
                    stop.set()
                if c == "x":
                    stop.set()
                    sys.exit()
                if c == "1":
                    print "starting t1"
                    t1.start()
                if c == "2":
                    print "starting t2"
                    t2.start()
            print "waiting for {} threads to close".format(threading.active_count() - 1)
            for t in [t1, t2]:
                t.join()
            stop.clear()
            print "stop has been triggered and reset... restart"
    finally:
        print "done!"