Une fonction qui accepte une valeur et renvoie une autre valeur, et ne perturbe rien en dehors de la fonction, n'a aucun effet secondaire et est donc thread-safe. Si vous souhaitez prendre en compte des éléments tels que la manière dont la fonction s'exécute affecte la consommation d'énergie, le problème est différent.
Je suppose que vous faites référence à une machine complète de Turing qui exécute une sorte de langage de programmation bien défini, où les détails de mise en œuvre ne sont pas pertinents. En d’autres termes, l’utilisation de la pile n’a aucune importance, si la fonction que j’écris dans le langage de programmation de mon choix peut garantir l’immuabilité dans les limites du langage. Je ne pense pas à la pile lorsque je programme dans un langage de haut niveau, je ne devrais pas non plus le faire.
Pour illustrer comment cela fonctionne, je vais vous proposer quelques exemples simples en C #. Pour que ces exemples soient vrais, nous devons faire quelques hypothèses. Premièrement, le compilateur respecte la spécification C # sans erreur et deuxièmement, il génère les programmes corrects.
Supposons que je souhaite une fonction simple qui accepte une collection de chaînes et renvoie une chaîne qui est une concaténation de toutes les chaînes de la collection, séparées par des virgules. Une implémentation simple et naïve en C # pourrait ressembler à ceci:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
string result = string.Empty;
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result += s;
else
result += ", " + s;
}
return result;
}
Cet exemple est immuable, à première vue. Comment je sais ça? Parce que l' string
objet est immuable. Cependant, la mise en œuvre n'est pas idéale. Étant donné qu’il result
est immuable, un nouvel objet chaîne doit être créé à chaque fois dans la boucle, en remplacement de l’objet original pointé result
vers. Cela peut affecter négativement la vitesse et faire pression sur le ramasse-miettes, car il doit nettoyer toutes ces chaînes supplémentaires.
Maintenant, disons que je fais ceci:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
var result = new StringBuilder();
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
Notez que je l' ai remplacé string
result
par un objet mutable, StringBuilder
. C'est beaucoup plus rapide que le premier exemple, car une nouvelle chaîne n'est pas créée à chaque fois dans la boucle. Au lieu de cela, l'objet StringBuilder ajoute simplement les caractères de chaque chaîne à une collection de caractères et affiche le tout à la fin.
Cette fonction est-elle immuable, même si StringBuilder est modifiable?
Oui, ça l'est. Pourquoi? Parce que chaque fois que cette fonction est appelée, un nouveau StringBuilder est créé, uniquement pour cet appel. Nous avons donc maintenant une fonction pure compatible avec les threads, mais contenant des composants mutables.
Mais si je faisais ça?
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
public string ConcatenateWithCommas(ImmutableList<string> list)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
Cette méthode est-elle thread-safe? Non ce n'est pas. Pourquoi? Parce que la classe détient maintenant l’état dont dépend ma méthode. Une condition de concurrence critique est maintenant présente dans la méthode: un thread peut être modifié IsFirst
, mais un autre peut effectuer le premier Append()
. Dans ce cas, j'ai une virgule au début de ma chaîne qui n'est pas supposée s'y trouver.
Pourquoi pourrais-je vouloir le faire comme ça? Eh bien, je souhaiterais peut-être que les threads accumulent les chaînes dans mon result
ordre ou dans l'ordre d'arrivée des threads. Peut-être que c'est un enregistreur, qui sait?
Quoi qu'il en soit, pour résoudre ce problème, j'ai mis une lock
déclaration autour des entrailles de la méthode.
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
private static object locker = new object();
public string AppendWithCommas(ImmutableList<string> list)
{
lock (locker)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
}
Maintenant, il est à nouveau thread-safe.
La seule façon pour mes méthodes immuables de ne pas être thread-safe est que si la méthode perd en quelque sorte une partie de son implémentation. Cela pourrait-il arriver? Pas si le compilateur est correct et le programme est correct. Aurai-je besoin de verrous sur de telles méthodes? Non.
Pour voir un exemple de fuite possible dans une implémentation dans un scénario d'accès simultané, voir ici .
but everything has a side effect
- Non, non. Une fonction qui accepte une valeur et renvoie une autre valeur, et ne perturbe rien en dehors de la fonction, n'a aucun effet secondaire et est donc thread-safe. Peu importe que l'ordinateur utilise de l'électricité. Nous pouvons parler des rayons cosmiques frappant aussi les cellules de la mémoire, si vous voulez, mais gardons l’argument pratique. Si vous souhaitez prendre en compte des éléments tels que la manière dont la fonction s'exécute affecte la consommation d'énergie, le problème est différent de celui de la programmation threadsafe.