En la programació informàtica multifil, una funció és segura per a fils quan es pot invocar o accedir-hi simultàniament per diversos fils sense causar un comportament inesperat, condicions de carrera o corrupció de dades. Igual que en el context multifil on un programa executa diversos fils simultàniament en un espai d'adreces compartit i cadascun d'aquests fils té accés a la memòria de tots els altres fils, les funcions segures per a fils han de garantir que tots aquests fils es comportin correctament i compleixin les seves especificacions de disseny sense interacció no intencionada.
Hi ha diverses estratègies per crear estructures de dades segures per a fils d'execució.
Nivells de seguretat dels fils Els diferents proveïdors utilitzen una terminologia lleugerament diferent per a la seguretat dels fils, però la terminologia de seguretat dels fils més utilitzada és:
No és segur per a fils d'execució: Diferents fils d'execució no haurien d'accedir simultàniament a les estructures de dades. Segur per a fils, serialització: utilitza un únic mutex per a tots els recursos per garantir que el fil estigui lliure de condicions de carrera quan diversos fils accedeixen simultàniament a aquests recursos. Segur per a fils, segur per a MT: utilitza un mutex per a cada recurs per garantir que el fil estigui lliure de condicions de carrera quan diversos fils accedeixen simultàniament a aquests recursos.
Les garanties de seguretat dels fils d'execució també solen incloure passos de disseny per prevenir o limitar el risc de diferents formes de bloquejos, així com optimitzacions per maximitzar el rendiment concurrent. Tanmateix, no sempre es poden donar garanties sense bloquejos, ja que els bloquejos poden ser causats per retrollamades i violacions de la superposició arquitectònica independent de la biblioteca en si.
Les biblioteques de programari poden proporcionar certes garanties de seguretat per a fils. Per exemple, es pot garantir que les lectures concurrents siguin segures per a fils, però les escriptures concurrents poden no ser-ho. Que un programa que utilitza una biblioteca d'aquest tipus sigui segur per a fils depèn de si utilitza la biblioteca d'una manera coherent amb aquestes garanties.
Enfocaments d'implementació Es llisten dues classes d'enfocaments per evitar condicions de carrera per aconseguir la seguretat dels fils.
La primera classe d'enfocaments se centra en evitar l'estat compartit i inclou:
Reentrada Escriure codi de manera que pugui ser executat parcialment per un fil, executat pel mateix fil o executat simultàniament per un altre fil i, tot i així, completar correctament l'execució original. Això requereix desar la informació d'estat en variables locals a cada execució, normalment en una pila, en lloc de fer-ho en variables estàtiques o globals o altres estats no locals. S'ha d'accedir a tots els estats no locals mitjançant operacions atòmiques i les estructures de dades també han de ser reentrants. Emmagatzematge local de fils Les variables es localitzen de manera que cada fil d'execució té la seva pròpia còpia privada. Aquestes variables conserven els seus valors a través de subrutines i altres límits de codi i són segures per a fils d'execució, ja que són locals per a cada fil d'execució, tot i que el codi que hi accedeix pot ser executat simultàniament per un altre fil d'execució. Objectes immutables L'estat d'un objecte no es pot canviar després de la construcció. Això implica tant que només es comparteixen dades de només lectura com que s'aconsegueix la seguretat inherent dels fils. Les operacions mutables (no constants) es poden implementar de manera que creïn nous objectes en lloc de modificar els existents. Aquest enfocament és característic de la programació funcional i també l'utilitzen les implementacions de cadenes a Java, C# i Python. (Vegeu Objecte immutable.)
La segona classe d'enfocaments estan relacionats amb la sincronització i s'utilitzen en situacions on no es pot evitar l'estat compartit:
Exclusió mútua L'accés a les dades compartides es serialitza mitjançant mecanismes que garanteixen que només un fil llegeixi o escrigui a les dades compartides en qualsevol moment. La incorporació de l'exclusió mútua s'ha de pensar bé, ja que un ús inadequat pot provocar efectes secundaris com ara bloquejos intermedis, bloquejos directes i manca de recursos. Operacions atòmiques L'accés a les dades compartides s'utilitza mitjançant operacions atòmiques que no poden ser interrompudes per altres fils d'execució. Això normalment requereix l'ús d'instruccions especials en llenguatge màquina, que poden estar disponibles en una biblioteca d'execució. Com que les operacions són atòmiques, les dades compartides sempre es mantenen en un estat vàlid, independentment de com hi accedeixin altres fils d'execució. Les operacions atòmiques formen la base de molts mecanismes de bloqueig de fils i s'utilitzen per implementar primitives d'exclusió mútua.
Exemples En el següent fragment de codi Java, la paraula clau Java synchronized fa que el mètode sigui thread-safe:class Counter { private int i = 0;
public synchronized void inc() { i++; } }En el llenguatge de programació C, cada fil té la seva pròpia pila. Tanmateix, una variable estàtica no es manté a la pila; tots els fils hi comparteixen accés simultani. Si diversos fils se superposen mentre executen la mateixa funció, és possible que un fil canviï una variable estàtica mentre un altre està a mig camí de la seva comprovació. Aquest error lògic difícil de diagnosticar, que pot compilar-se i executar-se correctament la major part del temps, s'anomena condició de carrera. Una manera habitual d'evitar-ho és utilitzar una altra variable compartida com a "bloqueig" o "mutex" (d' exclusió mútua).
En el següent fragment de codi C, la funció és segura per a fils, però no és reentrant:# include <pthread.h>
int increment_counter () { static int counter = 0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// only allow one thread to increment at a time pthread_mutex_lock(&mutex);
++counter;
// store value before any other threads increment it further int result = counter;
pthread_mutex_unlock(&mutex);
return result; }En l'anterior, increment_counter pot ser cridat per diferents fils d'execució sense cap problema, ja que s'utilitza un mutex per sincronitzar tots els accessos a la variable counter compartida. Però si la funció s'utilitza en un gestor d'interrupcions reentrant i sorgeix una segona interrupció mentre el mutex està bloquejat, la segona rutina es penjarà per sempre. Com que el servei d'interrupcions pot desactivar altres interrupcions, tot el sistema podria patir-ne les conseqüències.
Referències
Categoria:Llenguatges de programació