¿Cuál es la diferencia entre los atributos atómicos y no atómicos?

Resuelto Alex Wayne asked hace 54 años • 28 respuestas

¿Qué significan atomicy qué nonatomicsignifican en las declaraciones de propiedad?

@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;

¿Cuál es la diferencia operativa entre estos tres?

Alex Wayne avatar Jan 01 '70 08:01 Alex Wayne
Aceptado

Los dos últimos son idénticos; "atómico" es el comportamiento predeterminado ( tenga en cuenta que en realidad no es una palabra clave; se especifica solo por la ausencia denonatomic -- atomicse agregó como palabra clave en versiones recientes de llvm/clang).

Suponiendo que está sintetizando las implementaciones del método, atómico versus no atómico cambia el código generado. Si está escribiendo sus propios definidores/obtenedores, atómico/nonatomic/retain/assign/copy son meramente consultivos. (Nota: @synthesize es ahora el comportamiento predeterminado en versiones recientes de LLVM. Tampoco es necesario declarar variables de instancia; también se sintetizarán automáticamente y tendrán un _antepuesto a su nombre para evitar el acceso directo accidental).

Con "atómico", el setter/getter sintetizado garantizará que siempre se devuelva un valor completo del getter o que el setter lo establezca, independientemente de la actividad del setter en cualquier otro hilo. Es decir, si el subproceso A está en el medio del captador mientras el subproceso B llama al definidor, se devolverá un valor viable real (muy probablemente un objeto liberado automáticamente) a la persona que llama en A.

En nonatomic, no se ofrecen tales garantías. Por tanto, nonatomices considerablemente más rápido que el "atómico".

Lo que "atomic" no hace es ofrecer garantías sobre la seguridad de los subprocesos. Si el subproceso A llama al captador simultáneamente con el subproceso B y C llamando al definidor con valores diferentes, el subproceso A puede obtener cualquiera de los tres valores devueltos: el anterior a la llamada de cualquier definidor o cualquiera de los valores pasados ​​a los definidores. en B y C. Del mismo modo, el objeto puede terminar con el valor de B o C, no hay forma de saberlo.

Garantizar la integridad de los datos, uno de los principales desafíos de la programación multiproceso, se logra por otros medios.

Sumando a esto:

atomicityde una sola propiedad tampoco puede garantizar la seguridad de los subprocesos cuando están en juego varias propiedades dependientes.

Considerar:

 @property(atomic, copy) NSString *firstName;
 @property(atomic, copy) NSString *lastName;
 @property(readonly, atomic, copy) NSString *fullName;

En este caso, el hilo A podría cambiar el nombre del objeto llamando setFirstName:y luego llamando setLastName:. Mientras tanto, el subproceso B puede llamar fullNameentre las dos llamadas del subproceso A y recibirá el nuevo nombre junto con el apellido anterior.

Para abordar esto, necesita un modelo transaccional . Es decir, algún otro tipo de sincronización y/o exclusión que permita excluir el acceso fullNamemientras se actualizan las propiedades dependientes.

bbum avatar Feb 26 '2009 06:02 bbum

Esto se explica en la documentación de Apple , pero a continuación se muestran algunos ejemplos de lo que realmente está sucediendo.

Tenga en cuenta que no existe una palabra clave "atómica". Si no especifica "nonatomic", entonces la propiedad es atómica, pero especificar "atómica" explícitamente generará un error.

Si no especifica "nonatomic", entonces la propiedad es atómica, pero aún puede especificar "atomic" explícitamente en versiones recientes si así lo desea.

//@property(nonatomic, retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

Ahora bien, la variante atómica es un poco más complicada:

//@property(retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName_ retain];
      [userName release];
      userName = userName_;
    }
}

Básicamente, la versión atómica tiene que tomar un bloqueo para garantizar la seguridad del subproceso, y también aumenta el recuento de referencias del objeto (y el recuento de liberación automática para equilibrarlo) para que se garantice que el objeto existe para la persona que llama; es una posible condición de carrera si otro subproceso establece el valor, lo que hace que el recuento de referencias caiga a 0.

En realidad, hay una gran cantidad de variantes diferentes de cómo funcionan estas cosas dependiendo de si las propiedades son valores escalares u objetos, y cómo interactúan retener, copiar, solo lectura, no atómicas, etc. En general, los sintetizadores de propiedades sólo saben cómo hacer "lo correcto" para todas las combinaciones.

Louis Gerbarg avatar Feb 26 '2009 06:02 Louis Gerbarg

Atómico

  • es el comportamiento predeterminado
  • garantizará que la CPU complete el proceso actual, antes de que otro proceso acceda a la variable
  • no es rápido, ya que garantiza que el proceso se complete por completo

No atómico

  • NO es el comportamiento predeterminado
  • más rápido (para código sintetizado, es decir, para variables creadas usando @property y @synthesize)
  • no es seguro para subprocesos
  • puede resultar en un comportamiento inesperado, cuando dos procesos diferentes acceden a la misma variable al mismo tiempo
raw3d avatar May 25 '2012 10:05 raw3d

La mejor manera de entender la diferencia es utilizando el siguiente ejemplo.

Supongamos que hay una propiedad de cadena atómica llamada "nombre", y si llama [self setName:@"A"]desde el subproceso A, llama [self setName:@"B"]desde el subproceso B y llama [self name]desde el subproceso C, entonces todas las operaciones en diferentes subprocesos se realizarán en serie, lo que significa que si un subproceso está ejecutando un definidor o getter, entonces otros hilos esperarán.

Esto hace que la propiedad "nombre" sea segura en lectura/escritura, pero si otro subproceso, D, llama [name release]simultáneamente, esta operación podría producir un bloqueo porque no hay ninguna llamada de establecimiento/obtención involucrada aquí. Lo que significa que un objeto es seguro para lectura/escritura (ATOMIC), pero no es seguro para subprocesos, ya que otros subprocesos pueden enviar simultáneamente cualquier tipo de mensaje al objeto. El desarrollador debe garantizar la seguridad de los subprocesos para dichos objetos.

Si la propiedad "nombre" no era atómica, entonces todos los subprocesos del ejemplo anterior: A, B, C y D se ejecutarán simultáneamente produciendo cualquier resultado impredecible. En el caso de atómico, A, B o C se ejecutará primero, pero D aún puede ejecutarse en paralelo.

Vijayendra avatar Jan 31 '2012 18:01 Vijayendra

La sintaxis y la semántica ya están bien definidas en otras excelentes respuestas a esta pregunta. Debido a que la ejecución y el rendimiento no se detallan bien, agregaré mi respuesta.

¿Cuál es la diferencia funcional entre estos 3?

Siempre había considerado que lo atómico era bastante curioso. En el nivel de abstracción en el que trabajamos, usar propiedades atómicas para una clase como vehículo para lograr el 100% de seguridad de subprocesos es un caso de esquina. Para programas multiproceso verdaderamente correctos, es casi seguro que la intervención del programador es un requisito. Mientras tanto, las características de rendimiento y la ejecución aún no se han detallado en profundidad. Después de haber escrito algunos programas con muchos subprocesos a lo largo de los años, había estado declarando mis propiedades nonatomictodo el tiempo porque atomic no era sensato para ningún propósito. Durante la discusión de los detalles de las propiedades atómicas y no atómicas de esta pregunta , hice algunos perfiles y encontré algunos resultados curiosos.

Ejecución

De acuerdo. Lo primero que me gustaría aclarar es que la implementación del bloqueo está definida y abstraída por la implementación. Louis usa @synchronized(self)en su ejemplo: he visto esto como una fuente común de confusión. La implementación en realidad no utiliza @synchronized(self); Utiliza bloqueos de giro a nivel de objeto . La ilustración de Louis es buena para una ilustración de alto nivel que utiliza construcciones que todos conocemos, pero es importante saber que no utiliza @synchronized(self).

Otra diferencia es que las propiedades atómicas retendrán/liberarán el ciclo de sus objetos dentro del captador.

Actuación

Aquí está la parte interesante: el rendimiento utilizando accesos a propiedades atómicas en casos no disputados (por ejemplo, de un solo subproceso) puede ser realmente muy rápido en algunos casos. En casos menos que ideales, el uso de accesos atómicos puede costar más de 20 veces los gastos generales de nonatomic. Mientras que el caso impugnado que utilizó 7 subprocesos fue 44 veces más lento para la estructura de tres bytes ( Core i7 Quad Core de 2,2 GHz, x86_64). La estructura de tres bytes es un ejemplo de propiedad muy lenta.

Nota al margen interesante: los descriptores de acceso definidos por el usuario de la estructura de tres bytes fueron 52 veces más rápidos que los descriptores de acceso atómicos sintetizados; o el 84% de la velocidad de los accesores no atómicos sintetizados.

Los objetos en los casos controvertidos también pueden exceder de 50 veces.

Debido a la cantidad de optimizaciones y variaciones en las implementaciones, es bastante difícil medir los impactos del mundo real en estos contextos. Es posible que a menudo escuches algo como "Confía, a menos que hagas un perfil y descubras que es un problema". Debido al nivel de abstracción, en realidad es bastante difícil medir el impacto real. Obtener los costos reales de los perfiles puede llevar mucho tiempo y, debido a las abstracciones, ser bastante inexacto. Además, ARC vs MRC puede marcar una gran diferencia.

Entonces, retrocedamos, sin centrarnos en la implementación de accesos a propiedades, incluiremos los sospechosos habituales como objc_msgSendy examinaremos algunos resultados de alto nivel del mundo real para muchas llamadas a un NSStringcaptador en casos no disputados (valores en segundos):

  • MRC | no atómico | captadores implementados manualmente: 2
  • MRC | no atómico | captador sintetizado: 7
  • MRC | atómico | getter sintetizado: 47
  • ARCO | no atómico | captador sintetizado: 38 (nota: el ciclo de recuento de referencias agregado de ARC aquí)
  • ARCO | atómico | getter sintetizado: 47

Como probablemente habrás adivinado, la actividad/ciclismo del recuento de referencia contribuye significativamente con los atómicos y bajo ARC. También se verían mayores diferencias en los casos impugnados.

Aunque presto mucha atención al rendimiento, sigo diciendo ¡ Semántica primero! . Mientras tanto, el rendimiento es una prioridad baja para muchos proyectos. Sin embargo, conocer los detalles de ejecución y los costos de las tecnologías que utiliza ciertamente no hace daño. Debe utilizar la tecnología adecuada para sus necesidades, propósitos y habilidades. Con suerte, esto le ahorrará algunas horas de comparaciones y le ayudará a tomar una decisión mejor informada al diseñar sus programas.

justin avatar Aug 18 '2012 09:08 justin