Domanda

EDIT: ho taggato questo C nella speranza di ottenere più risposte. È più la teoria che mi interessa che una specifica implementazione del linguaggio. Quindi, se sei un programmatore C, considera il seguente PHP come pseudo-codice e sentiti libero di rispondere con una risposta scritta in C.

Sto cercando di velocizzare uno script CLI PHP facendolo eseguire in parallelo invece che in serie. Le attività sono completamente indipendenti l'una dall'altra, quindi non importa in quale ordine iniziano / finiscono.

Ecco lo script originale (nota che tutti questi esempi sono rimossi per chiarezza):

<?php

$items = range(0, 100);

function do_stuff_with($item) { echo "$item\n"; }

foreach ($items as $item) {
    do_stuff_with($item);
}

Sono riuscito a farlo funzionare su $ items in parallelo con pcntl_fork () come mostrato di seguito:

<?php

ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);

$items = range(0, 100);

function do_stuff_with($item) { echo "$item\n"; }

$pids = array();
foreach ($items as $item) {
    $pid = pcntl_fork();
    if ($pid == -1) {
        die("couldn't fork()");
    } elseif ($pid > 0) {
        // parent
        $pids[] = $pid;
    } else {
        // child
        do_stuff_with($item);
        exit(0);
    }   
}

foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

Ora voglio estenderlo, quindi c'è un massimo di, diciamo, 10 bambini attivi contemporaneamente. Qual è il modo migliore di gestirlo? Ho provato alcune cose ma non ho avuto molta fortuna.

È stato utile?

Soluzione

Non esiste alcuna syscall per ottenere un elenco di pid figlio, ma ps può farlo per te.

L'opzione

--ppid elencherà tutti i figli per il tuo processo, quindi devi solo contare il numero di linee prodotte da ps .

In alternativa puoi mantenere il tuo contatore che aumenterai su fork () e diminuirai sul segnale SIGCHLD , supponendo che ppid rimanga invariato per fork'ed elaborato.

Altri suggerimenti

La cosa migliore che posso inventare è aggiungere tutte le attività a una coda, avviare il numero massimo di thread desiderati, quindi fare in modo che ciascun thread richieda un'attività dalla coda, esegua l'attività e richieda quella successiva . Non dimenticare di terminare i thread quando non ci sono più attività da svolgere.

Il forking è un'operazione costosa. A quanto pare, quello che vuoi davvero è multi threading , non multi elaborazione . La differenza è che i thread hanno un peso molto più leggero rispetto ai processi, poiché i thread condividono uno spazio di indirizzi virtuali ma i processi hanno spazi di indirizzi virtuali separati.

Non sono uno sviluppatore di PHP, ma una rapida ricerca su Google rivela che PHP non supporta il multithreading in modo nativo, ma ci sono librerie per fare il lavoro.

Comunque, una volta che hai capito come generare i fili, dovresti capire quanti fili devono essere generati. Per fare ciò, devi sapere qual è il collo di bottiglia della tua applicazione. Il collo di bottiglia è CPU, memoria o I / O? Nei tuoi commenti hai indicato di essere vincolato alla rete e che la rete è un tipo di I / O.

Se eri legato alla CPU, otterrai solo tanto parallelismo quanto i core della CPU; altri thread e stai solo perdendo tempo a fare cambi di contesto. Supponendo che tu possa capire quanti thread totali devono essere generati, dovresti dividere il tuo lavoro in tante unità e far sì che ogni thread elabori un'unità in modo indipendente.

Se fossi legato alla memoria, il multithreading non sarebbe di aiuto.

Dato che sei legato all'I / O, capire quanti thread generare è un po 'più complicato. Se tutti gli oggetti di lavoro impiegano all'incirca lo stesso tempo per l'elaborazione con una varianza molto bassa, è possibile stimare il numero di thread da generare misurando il tempo impiegato da un articolo di lavoro. Tuttavia, poiché i pacchetti di rete tendono ad avere latenze molto variabili, è improbabile che ciò avvenga.

Un'opzione consiste nell'utilizzare pool di thread: si crea un intero gruppo di thread e quindi, per ciascun elemento da elaborare, viene visualizzato se esiste un thread gratuito nel pool. Se c'è, hai quel thread che esegue il lavoro e passi all'elemento successivo. Altrimenti, aspetti che un thread diventi disponibile. La scelta della dimensione del pool di thread è importante - troppo grande e stai perdendo tempo a fare cambi di contesto non necessari. Troppi, e stai aspettando discussioni troppo spesso.

Ancora un'altra opzione è abbandonare il multithreading / multiprocessing e fare invece I / O asincroni. Dato che hai menzionato che stai lavorando su un processore single-core, questa sarà probabilmente l'opzione più veloce. Puoi utilizzare funzioni come socket_select () per verificare se un socket ha dati disponibili. In tal caso, è possibile leggere i dati, altrimenti si passa a un socket diverso. Ciò richiede molto più contabilità, ma si evita di attendere che i dati vengano inseriti in un socket quando i dati sono disponibili in un socket diverso.

Se si desidera evitare thread e I / O asincroni e attenersi al multiprocessing, può comunque valere la pena se l'elaborazione per articolo è abbastanza costosa. Potresti quindi fare la divisione del lavoro in questo modo:

$my_process_index = 0;
$pids = array();

// Fork off $max_procs processes
for($i = 0; $i < $max_procs - 1; $i++)
{
  $pid = pcntl_fork();
  if($pid == -1)
  {
    die("couldn't fork()");
  }
  elseif($pid > 0)
  {
    // parent
    $my_process_index++;
    $pids[] = $pid
  }
  else
  {
    // child
    break;
  }
}

// $my_process_index is now an integer in the range [0, $max_procs), unique among all the processes
// Each process will now process 1/$max_procs of the items
for($i = $my_process_index; $i < length($items); $i += $max_procs)
{
  do_stuff_with($items[$i]);
}

if($my_process_index != 0)
{
  exit(0);
}

man 2 setrlimit

Sarà per utente che potrebbe essere comunque quello che vuoi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top