Dans tout bon site web, il est je pense très important de monter des informations en cache afin de limiter les aller-retour entre les serveurs web et la base de données. Je dirai même qu'il s'agit de la pierre angulaire de tout site internet qui est supposé tenir la charge. Je me propose donc de réaliser un petit cache au travers de 3 posts dont voici le premier. Le cache qui sera présenté ici devra donc pouvoir contenir des éléments (c'est un peu la base), faire expirer les éléments qui sont à l'intérieur et surtout être Thread-Safe !!!
Comme je l'indiquai dans le premier post, je compte "partager" mes développements avec ceux qui me liront, ceux qui me diront que c'est n'importe quoi ce que je raconte, mais aussi ceux, dont le nombre tends vers 0, qui me considèreront comme un dieu (l'espoir fait vivre n'est-ce pas ?) . C'est pourquoi il s'agira de notre code, notre Cache.
Comme dans beaucoup d'autres classe que nous écrirons, nous allons utiliser les Generics afin de typer notre cache. Cela nous permettra d'éviter de devoir caster nos object, mais aussi nous évitera le boxing / unboxing, ce qui devrait théoriquement améliorer aussi les performances.
Voici la première description de notre classe :
1: namespace JALUM.Utility.Cache
2: {3: public class Cache<K, V> : IDictionary<K, V>, IDisposable
4: { 5: } 6: }Comme tu peux le voir, cette dernière agira exactement comme un dictionnaire Key / Value.
Afin de vérifier quels éléments doivent être conservés dans le cache, et lesquels doivent être supprimer, nous allons utiliser un Timer, par conséquent nous implémenterons aussi l'interface IDisposable afin de libérer correctement toutes les ressources.
Nous allons nous contenter, à peu de choses près, d'encapsuler un Dictionnary<K, V> en le rendant Thread-Safe. La méthode la plus simple serait d'utiliser le mot clé lock fourni par le framework .net mais cette utilisation a deux principaux inconvénients :
- Elle peut lorsqu'elle est mal utilisée conduire à des Dead-Locks. Si je lock A pour traiter B qui elle même tente de locker A -> l'appli freeze...
- Elle induit des locks lorsque cela n'est pas nécessaire. Si deux threads tentent d'accéder en lecture à la même ressource, le mot clé lock bloque le second thread jusqu'à ce que le premier ait terminé. Or les deux threads auraient pu y accéder en même temps sans problèmes.
La seconde méthode, celle que nous utiliserons, est d'utiliser un ReaderWriterLock, ou sa nouvelle version dans le Framework 3.5 : ReaderWriterLockSlim, qui permet de différencier les locks dédiés à la lecture des locks dédiés à l'écriture. Grosso modo, on signale que l'on souhaite faire une lecture. Si aucun lock en écriture n'est posé, on accède directement à la ressource, peu importe si d'autres sont aussi en train d'y accéder en lecture ; si un lock en écriture est déjà posé, le thread attends que le thread en écriture ait terminé son boulot. Lorsque l'on signale vouloir acquérir un lock en écriture, le thread attends que les thread en lecture déjà en cours se terminent. Si de nouveau locks en lecture sont demandés, ils attendront que le thread ait obtenu son lock et ai pu faire son boulot avant d'avoir à leur tour le droit d'accéder à la ressource.
Son utilisation est relativement simple mais comme les geeks que nous sommes, mieux vaut pour nous 20 lignes de code que 5 lignes d'explications ...
La lecture :
1: _lock.EnterReadLock();2: try
3: { 4: 5: }6: finally
7: { 8: _lock.ExitReadLock(); 9: }L'écriture :
1: _lock.EnterWriteLock(); 2: try
3: { 4: 5: } 6: finally
7: { 8: _lock.ExitWriteLock(); 9: }L'upgrade de la lecture en écriture (cela consomme moins que relâcher le lock en lecture et en acquérir un nouveau en écriture) :
1: _lock.EnterUpgradeableReadLock(); 2: try
3: { 4: _lock.EnterWriteLock(); 5: try
6: { 7: } 8: finally
9: { 10: _lock.ExitWriteLock(); 11: } 12: } 13: finally
14: { 15: _lock.ExitUpgradeableReadLock(); 16: }Voila pour la première partie de cette magnifique classe. A bientôt pour la suite.
Aucun commentaire:
Enregistrer un commentaire