1627 votos

C 11 introdujo un modelo de memoria estandarizado. Qué significa? Y ¿cómo va a afectar a la programación en C ?

C++11 se introdujo un estandarizados modelo de memoria, pero ¿qué significa eso exactamente? Y cómo va a afectar a la programación en C++?

Herb Sutter dice aquí que,

El modelo de memoria significa que el código de C++ ahora se ha normalizado una biblioteca llamada independientemente de que el compilador y en qué plataforma se está ejecutando. No hay una manera estándar de control cómo diferentes hilos de conversación a la procesador memoria.

"Cuando usted está hablando acerca de la división de [código] a través de los diferentes núcleos que en la norma, estamos hablando de el modelo de memoria. Vamos a optimizar sin que se rompa la siguientes supuestos que la gente se va para hacer en el código", dijo Sutter.

Bueno, puedo memorizar este y otros párrafos disponible en línea (como he mi propio modelo de memoria desde su nacimiento :P) y puede incluso publicar como respuesta a las preguntas formuladas por los demás, pero para ser honesto, yo no entiendo esto.

Así que, lo que básicamente quiero saber es, los programadores de C++ utilizado para desarrollar aplicaciones multiproceso incluso antes, así que ¿qué importa si sus hilos POSIX, o Windows hilos, o C++11 hilos? ¿Cuáles son los beneficios? Quiero entender los detalles de bajo nivel.

También tengo la sensación de que el C++11 modelo de memoria es de alguna manera relacionado con C++11 multi-threading de apoyo, como a menudo veo a estos dos juntos. Si es así, ¿cómo es exactamente? ¿Por qué deben estar relacionados?

Como no sé cómo interna de multi-threading funciona, y qué modelo de memoria de medios en general, por favor, que me ayude a comprender estos conceptos. :-)

1878voto

Nemo Puntos 32838

En primer lugar, usted tiene que aprender a pensar como un Lenguaje de Abogado.

El C++ especificación no hacen referencia a ningún compilador, sistema operativo o la CPU. Se hace referencia a una máquina abstracta que es una generalización de los sistemas reales. En el Lenguaje de Abogado mundo, el trabajo del programador es escribir el código para la máquina abstracta; el trabajo del compilador es actualizar ese código en un hormigón de la máquina. Mediante la codificación rígidamente a la especificación, puede estar seguro de que el código se compile y se ejecute sin modificación en cualquier sistema compatible con un compilador de C++, ya sea hoy o dentro de 50 años.

La máquina abstracta en el C++98/C++03 especificación es fundamentalmente un único subproceso. Por lo que no es posible escribir multi-threaded código C++ que es "totalmente portátil" con respecto a la especificación. La especificación no nos dice nada acerca de la atomicidad de cargas de memoria y almacena o el orden en el que las cargas y las tiendas que podría suceder, por no hablar de cosas como los mutexes.

Por supuesto, usted puede escribir multi-threaded código, en la práctica, para concreto, de los sistemas-como pthreads o Windows. Pero no existe ningún estándar en la forma de escribir multi-roscado de código para C++98/C++03.

La máquina abstracta en C++11 es multi-hilo por diseño. También tiene un sistema bien definido por el modelo de memoria; es decir, dice lo que el compilador puede y no puede hacer cuando se trata de acceder a la memoria.

Considere el siguiente ejemplo, en donde un par de variables globales son accesibles al mismo tiempo por dos subprocesos:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

Lo que podría Hilo 2 salida?

En C++98/C++03, esto no es un Comportamiento Indefinido; la pregunta en sí misma es sin sentido debido a que la norma no contempla algo llamado un "hilo".

En C++11, el resultado es un Comportamiento Indefinido, debido a que las cargas y las tiendas no necesitan ser atómica en general. Que puede no parecer mucho de una mejora... Y por sí mismo, no lo es.

Pero con C++11, puedes escribir esto:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

Ahora las cosas se ponen mucho más interesantes. Primero de todo, el comportamiento que aquí se definen. Subproceso 2 podía imprimir 0 0 (si se ejecuta antes que el Hilo 1), 37 17 (si se ejecuta después de que el Subproceso 1), o 0 17 (si se ejecuta después de que el Hilo 1 se asigna a x, pero antes de que asigna a y).

Lo que no se puede imprimir es 37 0, debido a que el modo por defecto para atómica de carga/tiendas en C++11, es cumplir la consistencia secuencial. Esto simplemente significa que todas las cargas y las tiendas deben ser "como si" lo que pasó en el orden en que se escribió dentro de cada hilo, mientras que las operaciones entre hilos pueden ser intercalados sin embargo, el sistema le gusta. Así, el comportamiento predeterminado de atomics proporciona tanto la atomicidad y la ordenación de las cargas y de las tiendas.

Ahora, en una CPU moderna, asegurando la consistencia secuencial puede ser costoso. En particular, el compilador es probable que emiten en toda regla de la memoria de las barreras entre cada acceso aquí. Pero si el algoritmo puede tolerar fuera de la orden de carga y almacenes; es decir, si se requiere la atomicidad pero no ordena; es decir, si se puede tolerar 37 0 como resultado de este programa, usted puede escribir esto:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

El más moderno, el de la CPU, la más probable es que esto sea más rápido que el anterior ejemplo.

Por último, si lo que necesita para mantener particular, de carga y almacena en fin, se puede escribir:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

Esto nos lleva de nuevo a la orden de carga y tiendas-así que 37 0 ya no es más una posible salida, pero lo hace con una sobrecarga mínima. (En este ejemplo trivial, el resultado es el mismo en toda regla consistencia secuencial; en un programa más amplio, que no sería.)

Por supuesto, si las únicas salidas que quiere ver son 0 0 o 37 17, sólo se puede envolver una exclusión mutua de todo el código original. Pero si usted ha leído hasta aquí, apuesto a que ya saben cómo funciona, y esta respuesta ya es más de lo que pensaba :-).

Así, la línea de fondo. Exclusiones mutuas son grandes, y C++11 se estandariza. Pero a veces, por razones de rendimiento que usted desea primitivas de nivel inferior (por ejemplo, el clásico doble control de bloqueo de patrón). El nuevo estándar proporciona un alto nivel de gadgets como los mutexes y variables de condición, y también proporciona un bajo nivel de gadgets como los tipos atómicos y los distintos sabores de la memoria de la barrera. Así que ahora usted puede escribir sofisticados, de alto rendimiento concurrente rutinas totalmente en el idioma especificado por el estándar, y puede estar seguro de que su código se compile y se ejecute sin cambios en ambos sistemas actuales y de mañana.

Aunque para ser franco, a menos que usted es un experto y trabajando en algunas graves de bajo nivel de código, probablemente debería seguir a los mutexes y variables de condición. Eso es lo que pretendo hacer.

Para más información sobre esto, vea este blog.

88voto

eran Puntos 12628

Este es ahora un niño de 2 años pregunta, pero de muy popular, vale la pena mencionar un fantástico recurso para el aprendizaje de C++11 modelo de memoria. No veo el punto en un resumen de su charla con el fin de hacer de esta otra respuesta completa, pero dado que este es el tipo que escribió la norma, creo que bien vale la pena ver la charla.

Herb Sutter tiene 3 horas de larga conversación acerca de C++11 modelo de memoria titulada "atómica<> Armas", disponible en el Channel9 sitio - parte 1 y parte 2. La charla es bastante técnica, y cubre los siguientes temas:

  1. Optimizaciones, Razas, y el Modelo de Memoria
  2. - Pedidos Qué: Adquirir y Liberación
  3. Ordenar: Mutexes, Atomics, y/o Cercas
  4. Otras Restricciones sobre Compiladores y Hardware
  5. Código Gen & Performance: x86/x64, IA64, el PODER, el BRAZO
  6. Relajado Atomics

La charla no elaborados en la API, sino en el razonamiento, en el fondo, bajo el capó y detrás de las escenas (¿sabía usted relajado semántica se han añadido a la norma sólo porque el Poder y el Brazo no admiten sincroniza carga de forma eficaz?).

69voto

Puppy Puntos 90818

Lo que significa es que la Norma define multi-threading, que define lo que ocurre en el contexto de múltiples subprocesos. Por supuesto, la gente usa diferentes implementaciones, pero eso es como preguntar por qué debemos tener un std::string cuando todos podemos ser el hogar utilizando un laminado string de la clase. Cuando usted está hablando acerca de hilos POSIX o Windows hilos, entonces esto es un poco de una ilusión cuando en realidad estamos hablando de x86 hilos, ya que es una función de hardware para ejecutar simultáneamente. El C++0x modelo de memoria hace que garantiza, ya sea x86 o ARM o Mips, o cualquier otra cosa que usted puede venir para arriba con.

50voto

ritesh Puntos 546

Para los idiomas no se especifica un modelo de memoria que usted está escribiendo código para el lenguaje y el modelo de memoria especificada por la arquitectura del procesador. El procesador puede optar por volver a fin de accesos a la memoria para el rendimiento. Así que, si el programa tiene carreras de datos (datos de la carrera es cuando su posible para múltiples núcleos / hyper-threading para acceder a la misma memoria simultáneamente), a continuación, su programa no es de la cruz de la plataforma debido a su dependencia en el procesador con el modelo de memoria. Usted puede consultar el procesador Intel o AMD de manuales de software para averiguar cómo los procesadores de mayo de re-orden accesos a la memoria.

Muy importante, cerraduras (y la concurrencia de la semántica con bloqueo) suelen ser implementado en una plataforma cruzada de manera... así que si usted está utilizando el estándar de bloqueos en un programa multiproceso sin carreras de datos, entonces usted no tiene que preocuparse acerca de la cruz de la plataforma de modelos de memoria.

Curiosamente, los compiladores de Microsoft para C++ semántica de adquirir y suelte para volátil que es un C++ extensión de lidiar con la falta de un modelo de memoria en C++ [http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx]. Sin embargo, dado que Windows se ejecuta en x86 / x64 solo, que no es decir mucho (Intel y AMD modelos de memoria, es fácil y eficiente para implementar adquirir y suelte la semántica de un lenguaje).

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by: