2017-08-21 6 views
1

Voici le scénario. J'ai un script Python qui lancera un autre programme (appelons le P) avant d'exécuter son code principal. Plusieurs scripts Python peuvent être couru en même temps, si le programme P a déjà commencé, il ne devrait pas être relancée:Sémaphore en mémoire partagée avec Python sous Windows?

pid 100 starts up 
pid 100 starts program P with pid n and detaches it 
pid 100 runs main 
pid 101 starts up 
pid 101 does not start program P because it's already started 
pid 101 runs main 
pid 100 main finishes 
pid 100 does not terminate program P because pid 101 needs it 
pid 100 terminates 
pid 101 main finishes 
pid 101 terminates program P with pid n 
pid 101 terminates 

Dans CI peut probablement créer une mémoire partagée par mmap ing un fichier et de mettre un sémaphore pour le suivre. Une fois que le sémaphore atteint 0, je peux également terminer le programme P. Mais je n'ai aucune idée de comment faire cela en Python et sur Windows.

Comment aborder ce problème, c'est-à-dire, y a-t-il une méthode déjà établie pour résoudre ce problème?

+0

Vous pouvez utiliser les touches [ 'mmap'] (https://docs.python.org/3/library/mmap.html) module avec un 'tagname'. La mémoire partagée dans Windows est un objet Section qui, comme plusieurs autres types d'objets noyau (par exemple, Périphériques, Événements) peut être nommé dans l'espace de noms de l'objet. Un nom tel que "share1234" est local à la session Windows en cours. Pour que le nom soit accessible à toutes les sessions, utilisez "Global \ share1234". Ce dernier doit utiliser la barre oblique inversée comme séparateur puisque la barre oblique est juste un caractère de nom dans le noyau. – eryksun

+0

La création d'un nom global utilise un séparateur de chemin en raison de la manière dont il est implémenté. L'API Windows utilise '\ BaseNamedObjects' pour ses noms globaux (toutes les sessions) et' \ Sessions \ [numéro de session] \ BaseNamedObjects' pour les noms locaux à une session. Il y a un lien symbolique "Global" dans le BNO local, donc cela résout vraiment cela comme, par exemple, '\ Sessions \ 1 \ BaseNamedObjects \ Global \ share1234' =>' \ BaseNamedObjects \ share1234'. – eryksun

Répondre

1

J'ai créé un exemple de la façon dont vous pouvez obtenir ce comportement sous Windows en utilisant des sockets plutôt qu'un sémaphore à mémoire partagée. Cela réalise la même chose; le programme C++ continuera à fonctionner tant qu'au moins un des scripts Python est en cours d'exécution. Une fois tous les scripts terminés, le programme C++ se termine tant qu'aucun autre script Python n'est démarré pendant une certaine période de temps.

La plupart du code va dans le programme C++, qui exécute un thread qui surveille les connexions TCP à partir du (des) script (s) Python.

Le script Python vérifie/démarre le programme Windows, puis ouvre un socket qui reste ouvert jusqu'à la fin du script.

Le programme Windows détecte les connexions et les déconnexions des sockets, en gardant une trace du moment où les scripts Python sont en cours d'exécution.

Dans ces exemples, le programme Windows est appelé "ConsoleApplication11.exe". J'ai utilisé le port 1234 et un délai de 15 secondes, vous pouvez les modifier dans les lignes 19-21 du programme C++. En outre, si vous voulez rendre la fin du programme C++ plus immédiate, appelez exit() au lieu de return à la fin de client_monitor_thread().

Espérons que cela puisse vous être utile.

script Python:

import socket 
import time 
import psutil 
import os 

# check if the C++ program is running, start it if not 
cppProgramName = "ConsoleApplication11.exe" 
progRunning = False 
for pid in psutil.pids(): 
    p = psutil.Process(pid) 
    if (cppProgramName in p.name()): 
     progRunning = True 

if not progRunning: 
    os.startfile(cppProgramName) 
    time.sleep(5) # wait for c++ program to start 

# open a socket to the C++ program to tell it we need it to keep running 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect(("127.0.0.1", 1234)) 

# (MAIN PROGRAM) 
time.sleep(3) 
# (END OF MAIN PROGRAM) 

# close the socket to the C++ program 
s.close() 

C++ programme:

// ConsoleApplication11.cpp : Defines the entry point for the console application. 
// 

#include "stdafx.h" 

#include <iostream> 
#include <set> 
#include <chrono> 
#include <thread> 

#include <winsock2.h> 
#include <Ws2tcpip.h> 

#pragma comment(lib, "ws2_32.lib")    // link with Ws2_32.lib 


namespace 
{ 
    const unsigned int commonPort   = 1234; // must match Python app 
    const unsigned int noClientsTimeoutLimit = 15;  // quit when no clients connected for this many seconds 
    bool    clientMonitorRunning = true; // flag to show client monitor is running 


    void client_monitor_thread() 
    { 
     // start up winsock service version 2.2 
     WSADATA wsaData; 
     int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 
     if (iResult != NO_ERROR) 
     { 
      std::cout << "WSAStartup() failed with error: " << iResult << std::endl; 
      clientMonitorRunning = false; 
      return; 
     } 

     // create a socket for listening for incoming connection requests. 
     SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
     if (listenSocket == INVALID_SOCKET) 
     { 
      std::cout << "socket() function failed with error: " << WSAGetLastError() << std::endl; 
      closesocket(listenSocket); 
      clientMonitorRunning = false; 
      return; 
     } 

     // sockaddr_in structure specifies the address family, IP address, and port for the socket 
     sockaddr_in service; 
     service.sin_family = AF_INET; 
     inet_pton(AF_INET, (PCSTR)"127.0.0.1", &(service.sin_addr)); 
     service.sin_port = htons(commonPort); 
     if (SOCKET_ERROR == bind(listenSocket, (SOCKADDR *)& service, sizeof(service))) 
     { 
      std::cout << "bind function failed with error " << WSAGetLastError() << std::endl; 
      closesocket(listenSocket); 
      clientMonitorRunning = false; 
      return; 
     } 

     // Listen for incoming connection requests on the created socket 
     if (SOCKET_ERROR == listen(listenSocket, SOMAXCONN)) 
     { 
      wprintf(L"listen function failed with error: %d\n", WSAGetLastError()); 
      closesocket(listenSocket); 
      clientMonitorRunning = false; 
      return; 
     } 

     std::cout << "Listening on port " << commonPort << std::endl; 

     // mow monitor client connections 
     std::set<unsigned int> activefds; 
     int timeoutCounter = 0; 

     while (clientMonitorRunning) 
     { 
      // check for existing clients disconnected 
      if (0 != activefds.size()) 
      { 
       std::set<unsigned int> disconnectedfds; 
       for (auto fd : activefds) 
       { 
        int flags = 0; 
        char buf[10]; 
        int rv = recv(fd, buf, 10, flags); 
        if (0 == rv) 
        { 
         disconnectedfds.insert(fd); 
        } 
       } 
       for (auto fd : disconnectedfds) 
       { 
        activefds.erase(fd); 
       } 
      } 

      // are any clients connected? do we need to quit? 
      if (0 == activefds.size()) 
      { 
       std::cout << "No clients - will exit in " << noClientsTimeoutLimit - timeoutCounter << " seconds" << std::endl; 
       ++timeoutCounter; 
       if (timeoutCounter == noClientsTimeoutLimit) 
       { 
        for (auto fd : activefds) 
        { 
         closesocket(fd); 
        } 
        closesocket(listenSocket); 
        clientMonitorRunning = false; 
        return; 
       } 
      } 
      else 
      { 
       timeoutCounter = 0; 
      } 

      // check for activity on the listening socket 
      fd_set readfds; 
      struct timeval timeout; 
      timeout.tv_sec = 1; 
      timeout.tv_usec = 0; 
      FD_ZERO(&readfds); 
      FD_SET(listenSocket, &readfds); 
      switch (select(sizeof(readfds), &readfds, NULL, NULL, &timeout)) 
      { 
      case 0: // timeout 
      { 
       break; 
      } 
      case SOCKET_ERROR: 
      { 
       std::cout << "listen failed with error: " << WSAGetLastError() << std::endl; 
       closesocket(listenSocket); 
       clientMonitorRunning = false; 
       return; 
      } 
      default: 
      { 
       if (FD_ISSET(listenSocket, &readfds)) 
       { 
        // accept the connection. 
        SOCKET fd = accept(listenSocket, NULL, NULL); 
        if (fd == INVALID_SOCKET) 
        { 
         std::cout << "accept failed with error: " << WSAGetLastError() << std::endl; 
         closesocket(listenSocket); 
         clientMonitorRunning = false; 
         return; 
        } 
        else 
        { 
         unsigned long nonBlock = 1; 
         ioctlsocket(fd, FIONBIO, &nonBlock); 
         activefds.insert(fd); 
        } 
       } 
       break; 
      } 
      } 
     } 

     return; 
    } 
} 


int main() 
{ 
    // start the client monitor thread, which will run until no clients are connected 
    std::thread clientMonitor(client_monitor_thread); 

    // placeholder for doing the actual work in this program 
    // loop until the client monitor thread exits 
    while (clientMonitorRunning) 
    { 
     std::this_thread::sleep_for(std::chrono::seconds(1)); 
    } 

    // clean up 
    clientMonitor.join(); 
    WSACleanup(); 
    return 0; 
} 
+0

Cette idée est intéressante, mais elle n'est pas sûre pour la programmation parallèle. (Une autre instance peut commencer avant "sinon progRunning" et lancer deux fois le programme.) J'ai réussi à résoudre le problème en créant un ["mutex"] (https://gist.github.com/derek1906/fab1773852362d45e024bacc239dcaf7) qui peut être utilisé dans différents processus à l'aide de verrous de fichiers Windows. –

+0

Je pense à cela et il est sûr pour la programmation parallèle. De par sa conception, 2 instances du programme C++ ne peuvent pas s'exécuter car les deux ne peuvent pas écouter sur le même port, et l'une échouera et quittera. Cependant, bien fait sur votre solution de mutex. –