> On 8 Dec 2021, at 17:11, Jen Kris <jenk...@tutanota.com> wrote: > > I started this post on November 29, and there have been helpful comments > since then from Barry Scott, Cameron Simpson, Peter Holzer and Chris > Angelico. Thanks to all of you. > > I've found a solution that works for my purpose, and I said earlier that I > would post the solution I found. If anyone has a better solution I would > appreciate any feedback. > > To recap, I'm using a pair of named pipes for IPC between C and Python. > Python runs as a child process after fork-execv. The Python program > continues to run concurrently in a while True loop, and responds to requests > from C at intervals, and continues to run until it receives a signal from C > to exit. C sends signals to Python, then waits to receive data back from > Python. My problem was that C was blocked when Python started. > > The solution was twofold: (1) for Python to run concurrently it must be a > multiprocessing loop (from the multiprocessing module), and (2) Python must > terminate its write strings with \n, or read will block in C waiting for > something that never comes. The multiprocessing module sidesteps the GIL; > without multiprocessing the GIL will block all other threads once Python > starts. > > Originally I used epoll() on the pipes. Cameron Smith and Barry Scott > advised against epoll, and for this case they are right. Blocking pipes work > here, and epoll is too much overhead for watching on a single file > descriptor. > > This is the Python code now: > > #!/usr/bin/python3 > from multiprocessing import Process
You already have feedback that multiprocessing is not required. > import os > > print("Python is running") > > child_pid = os.getpid() > print('child process id:', child_pid) > > def f(a, b): > > print("Python now in function f") > > pr = os.open('/tmp/Pipe_01', os.O_RDONLY) > print("File Descriptor1 Opened " + str(pr)) > pw = os.open('/tmp/Pipe_02', os.O_WRONLY) > print("File Descriptor2 Opened " + str(pw)) > > while True: > > v = os.read(pr,64) > print("Python read from pipe pr") > print(v) > > if v == b'99': > os.close(pr) > os.close(pw) > print("Python is terminating") > os._exit(os.EX_OK) > > if v != "Send child PID": > os.write(pw, b"OK message received\n") The \n should not be required as UDS (unix domain sockets) are message based not a stream of bytes. > print("Python wrote back") > > if __name__ == '__main__': > a = 0 > b = 0 > p = Process(target=f, args=(a, b,)) > p.start() > p.join() > > The variables a and b are not currently used in the body, but they will be > later. > > This is the part of the C code that communicates with Python: > > fifo_fd1 = open(fifo_path1, O_WRONLY); > fifo_fd2 = open(fifo_path2, O_RDONLY); > > status_write = write(fifo_fd1, py_msg_01, sizeof(py_msg_01)); > if (status_write < 0) perror("write"); You continue on after the error, exit would be better. > > status_read = read(fifo_fd2, fifo_readbuf, sizeof(py_msg_01)); > if (status_read < 0) perror("read"); > printf("C received message 1 from Python\n"); > printf("%.*s",(int)buf_len, fifo_readbuf); The length of the data read is in status_read not buf_len. > > status_write = write(fifo_fd1, py_msg_02, sizeof(py_msg_02)); How much data did you put into py_msg_01 buffer? Is it a C string? If so you want to write sizeof(py_msg_02)-1 to avoid sending the trailing NUL (0) of the C string. > if (status_write < 0) perror("write"); If it failed exit until you have figured out the error handling. > > status_read = read(fifo_fd2, fifo_readbuf, sizeof(py_msg_02)); > if (status_read < 0) perror("read"); > printf("C received message 2 from Python\n"); > printf("%.*s",(int)buf_len, fifo_readbuf); The length of the data read is in status_read not buf_len. At no point do I see in this C code the need for a \n in the data sent between python and C. > > // Terminate Python multiprocessing > printf("C is sending exit message to Python\n"); > status_write = write(fifo_fd1, py_msg_03, 2); Would be better to not be using "2" for the length to write here. If py_msg_03 only has the exit msg in it the use sizeof(py_msg_03). If its a C string the subtract 1 for the trailing NUL (0). > > printf("C is closing\n"); > close(fifo_fd1); > close(fifo_fd2); > > Screen output: > > Python is running > child process id: 5353 > Python now in function f > File Descriptor1 Opened 6 > Thread created 0 > File Descriptor2 Opened 7 > Process ID: 5351 > Parent Process ID: 5351 > I am the parent > Core joined 0 > I am the child > Python read from pipe pr > b'Hello to Python from C\x00\x00' The \x00 is because the length you used is wrong. > Python wrote back > C received message 1 from Python > OK message received > Python read from pipe pr > b'Message to Python 2\x00\x00' The \x00 is because the length you used is wrong. > Python wrote back > C received message 2 from Python > OK message received > C is sending exit message to Python > C is closing > Python read from pipe pr > b'99' > Python is terminating > > Python runs on a separate thread (created with pthreads) because I want the > flexibility of using this same basic code as a stand-alone .exe, or for a C > extension from Python called with ctypes. If I use it as a C extension then > I want the Python code on a separate thread because I can't have two > instances of the Python interpreter running on one thread, and one instance > will already be running on the main thread, albeit "suspended" by the call > from ctypes. > > So that's my solution: (1) Python multiprocessing module; (2) Python strings > written to the pipe must be terminated with \n. > > Thanks again to all who commented. > > > > Dec 6, 2021, 13:33 by ba...@barrys-emacs.org: > > >> On 6 Dec 2021, at 21:05, Jen Kris <jenk...@tutanota.com >> <mailto:jenk...@tutanota.com>> wrote: >> >> Here is what I don't understand from what you said. "The child process is >> created with a single thread—the one that called fork()." To me that >> implies that the thread that called fork() is the same thread as the child >> process. I guess you're talking about the distinction between logical >> threads and physical threads. > > The thread that called fork is cloned into a new thread in the new process. > It has a clone of the processor registers of the thread, stack memory segment > of that thread, > which means that it has all the stack variables and stack frames from the > parent process's thread. > > >> But the main issue is your suggestion that I should call fork-execv from the >> thread that runs the main C program, not from a separate physical pthread. >> That would certainly eliminate the overhead of creating a new pthread. > > Forget about physical threads, they only matter when you are performance > tuning your code. > >> I am working now to finish this, and I will try your suggestion of calling >> fork-execv from the "main" thread. When I reply back next I can give you a >> complete picture of what I'm doing. >> >> Your comments, and those of Peter Holzer and Chris Angelico, are most >> appreciated. > > Barry > > > -- https://mail.python.org/mailman/listinfo/python-list