2009-05-12 18 views
17

Tengo una tarea para escribir un sudoku de subprocesos múltiples, que encuentra todas las soluciones para un acertijo dado. Anteriormente escribí un sudoku de sudoku de rastreo de un único subproceso muy rápido, así que no necesito ayuda con el aspecto de sudoku.Algoritmo de subprocesos múltiples para resolver sudoku?

Mi problema probablemente esté relacionado con el hecho de no ganar concurrencia en realidad, pero no veo cómo este problema se beneficia con el multi-threading. No entiendo cómo puedes encontrar diferentes soluciones para el mismo problema al mismo tiempo sin mantener múltiples copias del rompecabezas. Dada esta suposición (por favor, demuestre que está equivocado), no veo cómo la solución de subprocesos múltiples es más eficiente que un subproceso único.

Le agradecería que si alguien me podría dar algunas sugerencias de partida para el algoritmo (por favor, no hay código ...)


me olvidó mencionar, el número de hilos que puede usar como se especifica un argumento para el programa, por lo que puedo decir, no está relacionado con el estado del rompecabezas de ninguna manera ...

Además, puede que no haya una solución única: una entrada válida puede estar totalmente vacía tablero. Tengo que informar min(1000, number of solutions) y mostrar uno de ellos (si existe)

+0

Ayudaría si describiera su algoritmo de subproceso único un poco más. –

+0

en realidad, es casi exactamente lo que has publicado :) Me parece que funciona bastante bien y es muy fácil de codificar ... – Jon

+0

De acuerdo, consulte mi nota adicional a continuación con una justificación simple para el multihilo. –

Respuesta

17

Bastante simple realmente.El concepto básico es que en su solución de retroceso se ramificaría cuando haya una opción. Probaste una rama, retrocediste y luego probé la otra opción.

Ahora, genere un hilo para cada opción y pruébelo simultáneamente. Solo engendre un nuevo hilo si hay < un número de hilos ya en el sistema (ese sería su argumento de entrada); de lo contrario, simplemente use una solución simple (es decir, su existente) de un único hilo. Para mayor eficiencia, obtenga estos subprocesos de trabajo de un grupo de subprocesos.

Esto es en muchos sentidos una técnica de dividir y conquistar, está utilizando las opciones como una oportunidad para dividir el espacio de búsqueda a la mitad y asignar la mitad a cada hilo. Lo más probable es que la mitad sea más difícil que el otro significado, la duración del hilo variará, pero eso es lo que hace que la optimización sea interesante.

La manera fácil de manejar los problemas obvios de sincronización es copiar el estado actual de la placa y pasarlo a cada instancia de su función, por lo que es un argumento de función. Esta copia significa que no tendrá que preocuparse por ninguna concurrencia compartida. Si su solución de un único subproceso usa una variable global o miembro para almacenar el estado de la placa, necesitará una copia de esta en la pila (fácil) o por subproceso (más difícil). Todo lo que necesita devolver es un estado de tablero y un número de movimientos para alcanzarlo.

Cada rutina que invoca varios subprocesos para hacer el trabajo debe invocar n-1 subprocesos cuando hay n trabajos, hacer la enésima tarea y luego esperar con un objeto de sincronización hasta que todos los demás subprocesos hayan finalizado. A continuación, evalúa sus resultados: tiene n estados de placa, devuelve el que tiene el menor número de movimientos.

+0

+1 buena sugerencia – ninesided

+0

un montón de excelentes consejos de todos, este parece ser el más adecuado para mi situación específica. Gracias de nuevo a todos :) – Jon

+2

Los métodos de concurrencia de Java tienen varios ejecutores que permiten un grupo de hilos delimitados. Investiga esos: hará tus sincronizaciones mucho más fáciles. –

2

Cuando dicen todas las soluciones a un rompecabezas dado, quiere usted decir la definitiva y única solución al rompecabezas? O el formas diferentes de llegar a la única solución? Comprendí que, por definición, un sudoku podría tener una sola solución ...

Para el primero, Pax's rule based approach o Tom Leys' take on multi-threading your existing backtracking algorithm podría ser el camino a seguir.

En este último caso, podría implementar algún tipo de algoritmo de bifurcación que ejecute un nuevo hilo (con su propia copia del rompecabezas) para cada movimiento posible en cada etapa del rompecabezas.

+0

Creo que un rompecabezas de Sudoku puede tener múltiples soluciones. Puede llegar al punto en el que tiene 4 celdas que pueden tener 1 de 2 números, cualquiera de los dos funcionará siempre que las otras celdas sean consistentes. Encontré esto mientras escribía un solucionador/hinter de Excel. – geofftnz

+0

si ese es el caso, entonces no es un sudoku verdadero, de lo contrario, muchas de las técnicas avanzadas que se basan en este principio son inútiles – ninesided

+0

Creo que estás en el punto del "verdadero sudoku". – geofftnz

1

Dependiendo de cómo haya codificado su solucionador de una sola hebra, es posible que pueda volver a utilizar la lógica. Puede codificar un solucionador de subprocesos múltiples para iniciar cada subproceso usando un conjunto diferente de estrategias para resolver el acertijo.

Usando esas estrategias diferentes, su solucionador de subprocesos múltiples puede encontrar el conjunto total de soluciones en menos tiempo que su solucionador de un solo subproceso (tenga en cuenta que un verdadero Sudoku solo tiene una solución ... usted no el único que tuvo que lidiar con ese horrible juego de dioses en clase)

0

Hace unos años, cuando consideré resolver el sudoku, parecía que la solución óptima usaba una combinación de algoritmos de análisis lógico y solo recurría a la fuerza bruta cuando sea necesario. Esto permitió al solucionador encontrar la solución muy rápidamente, y también clasificó el tablero por dificultad si quería usarlo para generar un nuevo rompecabezas. Si tomas este enfoque, ciertamente podrías introducir algo de simultaneidad, aunque hacer que los hilos funcionen juntos podría ser complicado.

2

¿Necesita beneficiarse del multihilo, o simplemente usar el multihebra para que pueda aprender para la tarea?

Si utiliza un algoritmo de fuerza bruta, es bastante fácil dividirlo en varios hilos, y si la tarea se centra en los hilos de codificación que pueden ser una solución aceptable.

5

La idea detrás de multi-threading es aprovechar tener varias CPU, lo que le permite hacer varios cálculos simultáneamente. Por supuesto, cada hilo va a necesitar su propia memoria, pero eso generalmente no es un problema.

Principalmente, lo que quiere hacer es dividir el posible estado de solución en varios subespacios que sean lo más independientes posible (para evitar desperdiciar demasiados recursos en la sobrecarga de creación de subprocesos) y "ajustar" su algoritmo (para beneficiarse realmente de tener múltiples hilos).

9

Multi-threading es útil en cualquier situación donde un solo hilo tiene que esperar un recurso y puede ejecutar otro hilo mientras tanto. Esto incluye un hilo esperando una solicitud de E/S o acceso a la base de datos, mientras que otro hilo continúa con el trabajo de la CPU.

multi-threading es también útil si los hilos individuales pueden ser arrendados a las CPU diffent (o núcleos), ya que entonces se ejecutan simultáneamente en verdad, a pesar de que en general, tendrán que compartir los datos por lo que todavía habrá algunos contención.

No puedo ver ningún motivo por el cual un solucionador de sudoku multihilo sea más eficiente que uno de un solo subproceso, simplemente porque no hay recursos a la espera. Todo se hará en la memoria.

Pero recuerdo algunos de los deberes que hice en Uni, y fue igualmente inútil (código de Fortran para ver qué tan profundo era un túnel cuando excavaba a 30 grados por una milla y luego a 15 grados por otra milla - sí, Soy bastante viejo :-). El punto es mostrar que puedes hacerlo, no que sea útil.

En el algoritmo.

Escribí un único solucionador de subprocesos que básicamente ejecutaba una serie de reglas en cada pasada para intentar llenar otro cuadrado. Una regla de muestra era: si la fila 1 solo tiene un cuadrado libre, el número es evidente de todos los otros números en la fila 1.

Había reglas similares para todas las filas, todas las columnas, todas las minirrejillas de 3x3. También había reglas que marcaban intersecciones entre filas y columnas (por ejemplo, si un cuadrado dado solo podía contener 3 o 4 debido a la fila y 4 o 7 debido a la columna, entonces era 4). Había reglas más complejas que no detallaré aquí, pero básicamente son de la misma manera que lo resuelves manualmente.

Sospecho que tienes reglas similares en tu implementación (ya que aparte de la fuerza bruta, no se me ocurre otra manera de resolverlo, y si has usado la fuerza bruta, no hay esperanza para ti :-).

Lo que sugeriría es asignar cada regla a una secuencia y hacer que compartan la grilla. Cada hilo haría su propia regla y solo esa regla.

Actualización:

Jon, basado en tu edición:

[editar] Se me olvidó mencionar, el número de hilos que se utilizarán se especifica como un argumento para el programa, por lo por lo que puedo decir, no está relacionado con el estado del rompecabezas de ninguna manera ...

Además, puede que no haya una solución única: una entrada válida puede ser una placa totalmente vacía.Tengo que informar el mínimo (1000, número de soluciones) y mostrar uno de ellos (si existe)

Parece que su profesor no quiere que se divida según las reglas, sino que en el tenedor puntos (donde se pueden aplicar múltiples reglas).

Con esto quiero decir, en cualquier punto de la solución, si hay dos o más movimientos posibles hacia adelante, debe asignar cada posibilidad a un hilo separado (aún usando sus reglas de eficiencia pero revisando concurrentemente cada posibilidad). Esto le daría una mejor concurrencia (suponiendo que los hilos se puedan ejecutar en CPU/núcleos separados) ya que no habrá contención para la placa; cada hilo obtendrá su propia copia.

Además, como está limitando el número de subprocesos, tendrá que trabajar un poco de magia de grupo de subprocesos para lograr esto.

Lo que sugeriría es tener una cola de trabajo y N hilos. La cola de trabajos está inicialmente vacía cuando el hilo principal inicia todos los hilos de trabajo. Luego, el hilo principal pone el estado inicial del rompecabezas en la cola de trabajo.

Los hilos de trabajo simplemente esperan a que se coloque un estado en la cola de trabajo y uno de ellos lo toma para procesarlo. El subproceso de trabajo es el solucionador de un único subproceso con una pequeña modificación: cuando hay X posibilidades de avanzar (X> 1), el trabajador vuelve a colocar X-1 en la cola de trabajo y luego continúa procesando la otra posibilidad.

Entonces, digamos que solo hay una solución (verdadero Sudoku :-). El primer hilo de trabajo se reducirá en la solución sin encontrar ninguna bifurcación y eso será exactamente como en su situación actual.

Pero con dos posibilidades en el movimiento 27 (digamos que 3 o 4 podrían ir a la celda superior izquierda), el hilo creará otro tablero con la primera posibilidad (poner 3 en esa celda) y colocarlo en la cola de trabajo . Luego pondría 4 en su propia copia y continuaría.

Otro hilo recogerá el tablero con 3 en esa celda y continuará. De esta forma, tiene dos hilos ejecutándose simultáneamente manejando las dos posibilidades.

Cuando un hilo decide que su placa es insoluble, lo tira y vuelve a la cola de trabajo para más trabajo.

Cuando un hilo decide que su placa está resuelta, notifica el hilo principal que puede almacenarlo, sobrescribe cualquier solución anterior (la solución encontrada es la primera) o la descarta si ya tiene una solución (último se encuentra la solución), luego el hilo de trabajo vuelve a la cola de trabajo para más trabajo. En cualquier caso, el hilo principal debería incrementar el número de soluciones encontradas.

Cuando todos los subprocesos están inactivos y la cola de trabajo está vacía, el principal tendrá o no una solución. También tendrá un recuento de soluciones.

Tenga en cuenta que todas las comunicaciones entre los trabajadores y el hilo principal deberán omitirse (supongo que lo sabe según la información de su pregunta).

+0

+1 al instante, aunque dependiendo del número de reglas o su efectividad en las diferentes etapas del rompecabezas, puede valer la pena mirar las reglas de agrupamiento en lugar de tener un hilo para cada regla. – ninesided

+0

Si la tarea es hacerla multihilo, podrías dividir las reglas en dos conjuntos y asignarles dos hilos. Pero también puedes recorrer todo el camino y asignar una regla por hilo. Me parece recordar que mi solución funcionó con solo 12 reglas (tenía una regla para todas las columnas, no para cada columna, etc.), que debería ser bastante fácil de subprocesar. – paxdiablo

+0

Pax: el algoritmo que esbozó probablemente solo funcione si hay una única solución. Una vez que hay dos opciones posibles, no hay movimientos "obvios" que hacer. Es en este punto que la división en dos hilos para probar ambas opciones se convierte en algo valioso. Dado que todas esas ramas y bucles de código toman tiempo, podría dividirlos en varios núcleos, una gran razón para usar la concurrencia. Mucho más efectivo que si tu programa estuviera vinculado a E/S (memoria, disco duro) –

1

Algunos puntos generales: no ejecuto procesos en paralelo a menos que 1) es fácil dividir el problema 2) Sé que obtendré un beneficio al hacerlo, p. No golpearé otro cuello de botella. Evito por completo compartir valores mutables entre hilos, o minimizarlos. Algunas personas son lo suficientemente inteligentes como para trabajar con seguridad con mutexes. No soy.

Necesita encontrar puntos en su algoritmo que creen ramas naturales o grandes unidades de trabajo.Una vez que ha identificado una unidad para trabajar, la coloca en una cola para que un hilo la recoja. Como un ejemplo trivial. 10 bases de datos para actualizar. Inicie la actualización asincrónica en los 10 servidores. Espera a que todos se completen. Puedo evitar fácilmente compartir estado entre subprocesos/procesos, y puedo agregar fácilmente los resultados.

Lo que le viene a la mente para el sudoku es que una solución de suduko eficiente debería combinar 2-3 estrategias (o más) que nunca pasen una cierta profundidad. Cuando hago Sudoku, es evidente que, en cualquier momento dado, diferentes algoritmos proporcionan la solución con menos trabajo. Podría simplemente disparar un puñado de estrategias, dejarlas investigar a una profundidad limitada, esperar el informe. Enjuague, repita. Esto evita la "fuerza bruta" de la solución. Cada algoritmo tiene su propio espacio de datos, pero combina las respuestas.

Sciam.com tenía un artículo sobre esto hace un año o dos - parece que no es público, sin embargo.

+0

Suduku tiene un espacio de búsqueda muy pequeño, dejando poco beneficio de la profundización iterativa que usted describe. Quizás en una tabla imaginaria 15 * 15 o 25 * 25. –

4

Aquí es un codicioso de fuerza bruta de un solo subproceso solucionador:

  1. Select siguiente celda vacía. Si no hay más células vacías, ¡victoria!
  2. Valor de celda posible = 1
  3. Verificar solución parcial no válida (duplicados en fila, columna o bloque 3x3).
  4. Si solución parcial no es válido, incrementar valor de la celda y regresar al paso 3. De lo contrario, vaya al paso 1.

Si nos fijamos en el esquema anterior, la combinación de los pasos 2 y 3 son candidatos obvios para multihilo. Las soluciones más ambiciosas implican la creación de una exploración recursiva que engendra tareas que se envían a un grupo de subprocesos.

EDITAR para responder a este punto: "No entiendo cómo puede encontrar diferentes soluciones para el mismo problema al mismo tiempo sin mantener varias copias del rompecabezas".

No puede. Ese es todo el punto. Sin embargo, un ejemplo concreto de 9 hilos podría aclarar los beneficios:

  1. Comience con un problema de ejemplo.
  2. Encuentra la primera celda vacía.
  3. Crea 9 subprocesos, donde cada subproceso tiene su propia copia del problema con su propio índice como valor candidato en la celda vacía.
  4. Dentro de cada hilo, ejecute su algoritmo de subproceso único original en esta copia modificada local del problema.
  5. Si uno de los hilos encuentra una respuesta, detenga todos los otros hilos.

Como se puede imaginar, cada subproceso ahora tiene un espacio de problema ligeramente menor y cada subproceso tiene el potencial de ejecutarse en su propio núcleo de CPU. Con un algoritmo de un solo subproceso solo, no puede aprovechar los beneficios de una máquina multi-core.

+1

Si sus 9 hilos de trabajo no pueden engendrar nuevos hilos propios, existe una buena posibilidad de que la mayoría de ellos se quede sin trabajo inmediatamente, dejándolo con solo 1 o 2 hilos. Necesita repetir la bifurcación en múltiples niveles para obtener una solución que se escalará a muchos procesadores. –

+0

En realidad, para casos realmente patológicos, no necesariamente encontrará los callejones sin salida tan rápidamente. Dicho esto, es cierto que un solucionador multiproceso sofisticado necesitaría mantener el conjunto de subprocesos bien abastecido con soluciones parciales para trabajar. Sin embargo, esta no es MI tarea ... ;-) –

+0

Por ejemplo, vea el sitio de Gordon Royle donde está generando problemas mínimos: http://people.csse.uwa.edu.au/gordon/sudokumin.php Very desagradable incluso con multihilo. –

1

Dijiste que usaste el seguimiento de retroceso para resolver el problema. Lo que puede hacer es dividir el espacio de búsqueda en dos y manejar cada espacio en un hilo, luego cada hilo haría lo mismo hasta llegar al último nodo. Hice una solución a esto que se puede encontrar en www2.cs.uregina.ca/~hmer200a pero con un solo hilo, pero el mecanismo de división del espacio de búsqueda está allí usando branch y bound.

0

Tengo una idea que es muy divertida aquí ... ¡hazlo con Actor Model! Yo diría que usar erlang ... ¿Cómo? Se empieza con el tablero original, y ..

  • 1) en la primera celda vacía crear 9 niños con número diferente, entonces suicidarse
  • 2) cada cheque niño si es válido, si lo que se suicida, otra cosa
    • si hay una celda vacía, ir a 1)
    • si completa, este actor es una solución

Claramente, cada actor sobreviviente es una solución al problema =)

0

Solo una nota al margen. De hecho, implementé un solucionador de sudoku optimizado y busqué multihilo, pero dos cosas me detuvieron.

En primer lugar, la simple sobrecarga de iniciar un hilo tomó 0,5 milisegundos, mientras que toda la resolución tomó entre 1 y 3 milisegundos (utilicé Java, otros lenguajes o entornos pueden dar resultados diferentes).

En segundo lugar, la mayoría de los problemas no requieren retroceso alguno. Y los que sí lo hacen, solo lo necesitan tarde en la resolución del problema, una vez que se han agotado todas las reglas del juego y tenemos que hacer una hipótesis.

0

Aquí está mi propio centavo. Espero eso ayude.

Recuerde que las comunicaciones entre procesadores y entre hilos son costosas. No multiplique a menos que tenga para. Si no hay mucho trabajo/computación para hacer en otros hilos, también podría continuar con un hilo único.

Intente tanto como sea posible a evite compartiendo datos entre hilos. Úselos solo cuando sea necesario

Aproveche Extensiones SIMD siempre que sea posible. Con las Extensiones de Vector puede realizar cálculos en múltiples datos de una sola vez. Puede ayudarte en abundancia.

Cuestiones relacionadas