8 votes

Les threads en attente d'un verrou respectent-ils la stratégie FIFO ?

Supposons que j'ai le code suivant

static class ...
{
    static object myobj = new object();

    static void mymethod()
    {
        lock(myobj)
        {
            // mon code....
        }
    }
}

Ensuite, supposons que pendant que le thread1 a le verrou, le thread2 essaie d'exécuter mymethod. Va-t-il attendre que le verrou soit libéré ou jeter une exception?

Si oui, l'ordre est-il assuré de sorte que si des threads supplémentaires arrivent, ils sont FIFO?

8voto

Ma réponse mise à jour : Ils sont en file d'attente, mais l'ordre n'est pas garanti d'être FIFO.

Consultez ce lien : http://www.albahari.com/threading/part2.aspx

3voto

Remus Rusanu Points 159382

Il n'est pas clair dans votre code comment myobj devient visible à l'intérieur de mymethod. On dirait que var myobj est une variable locale de pile dans le contexte de déclaration (puisqu'il s'agit de var). Dans ce cas, il se peut que chaque thread ait une instance séparée de celui-ci et que mymethod ne bloque pas.

Mise à jour

Concernant l'argument FIFO global, des informations de fond sont nécessaires : le CLR ne fournit pas de synchronisation. C'est l'hôte CLR qui fournit cela en tant que service à l'exécution CLR. L'hôte implémente IHostSyncManager et d'autres interfaces et fournit les diverses primitives de synchronisation. Cela peut sembler sans importance car l'hôte le plus courant est l'hôte d'application typique (c'est-à-dire que vous compilez dans un exe) et cela délègue toute la synchronisation au système d'exploitation (vos anciens primitives Petzold dans l'API Win32). Cependant, il existe au moins deux autres grands environnements d'hébergement : celui d'ASP.Net (je ne suis pas sûr de ce qu'il fait) et SQL Server. Ce que je peux dire avec certitude, c'est que SQL Server fournit toutes les primitives sur le dessus de l'SOS (qui est essentiellement un système d'exploitation plus utilisateur), ne touchant jamais aux primitives du système d'exploitation, et les primitives SOS sont injustes par conception pour éviter les convois de verrous (c'est-à-dire sans garantie de FIFO). Comme le lien dans l'autre réponse l'a déjà souligné, les primitives du système d'exploitation ont également commencé à fournir un comportement injuste, pour la même raison d'éviter les convois de verrous.

Pour plus d'informations sur les convois de verrous, vous devriez lire les articles de Rick Vicik sur Conception d'applications pour des performances élevées:

Convoi de verrou

Les verrous FIFO garantissent l'équité et la progression à tout prix, provoquant des convois de verrous. Le terme signifiait à l'origine plusieurs threads exécutant la même partie du code en groupe, entraînant plus de collisions que s'ils étaient distribués aléatoirement dans le code (tout comme les voitures regroupées par des feux de circulation). Le phénomène particulier dont je parle est pire car une fois qu'il se forme, le transfert implicite de propriété du verrou maintient les threads en cadence.

Pour illustrer, considérez l'exemple où un thread détient un verrou et est préempté tout en le possédant. Le résultat est que tous les autres threads s'accumuleront sur la liste d'attente pour ce verrou. Lorsque le thread préempté (propriétaire du verrou à ce moment-là) reprend son exécution et libère le verrou, il transmet automatiquement la propriété du verrou au premier thread de la liste d'attente. Ce thread peut ne pas s'exécuter pendant un certain temps, mais l'horloge du "temps de maintien" tourne. L'ancien propriétaire demande généralement le verrou à nouveau avant que la liste d'attente ne soit vidée, perpétuant le convoi.

1voto

MShevroja Points 32

Un exemple simple nous montre que l'ordre n'est pas garanti d'être FIFO

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication
{
    class Program
    {
        private static Info info = new Info();

        static void Main(string[] args)
        {
            Thread[] t1 = new Thread[5];
            for (int i = 0; i < 5; i++)
            {
                t1[i] = new Thread(info.DoWork);
            }

            Thread[] t2 = new Thread[5];
            for (int i = 0; i < 5; i++)
            {
                t2[i] = new Thread(info.Process);
            }

            for (int i = 0; i < 5; i++)
            {
                t1[i].Start();
                t2[i].Start();
            }

            Console.ReadKey();
        }
    }

    class Info
    {
        public object SynObject = new object();

        public void DoWork()
        {
            Debug.Print("DoWork Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId);
            lock (this.SynObject)
            {
                Debug.Print("Thread Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                Debug.Print("Thread Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId);
            }
        }

        public void Process()
        {
            Debug.Print("Process Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId);
            lock (this.SynObject)
            {
                Debug.Print("Process Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                Debug.Print("Process Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId);
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication
{
    class Program
    {
        private static Info info = new Info();

        static void Main(string[] args)
        {
            Thread[] t1 = new Thread[5];
            for (int i = 0; i < 5; i++)
            {
                t1[i] = new Thread(info.DoWork);
            }

            Thread[] t2 = new Thread[5];
            for (int i = 0; i < 5; i++)
            {
                t2[i] = new Thread(info.Process);
            }

            for (int i = 0; i < 5; i++)
            {
                t1[i].Start();
                t2[i].Start();
            }

            Console.ReadKey();
        }
    }

    class Info
    {
        public object SynObject = new object();

        public void DoWork()
        {
            Debug.Print("DoWork Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId);
            lock (this.SynObject)
            {
                Debug.Print("Thread Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                Debug.Print("Thread Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId);
            }
        }

        public void Process()
        {
            Debug.Print("Process Lock Reached: {0}", Thread.CurrentThread.ManagedThreadId);
            lock (this.SynObject)
            {
                Debug.Print("Process Lock Enter: {0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(5000);
                Debug.Print("Process Lock Exit: {0}", Thread.CurrentThread.ManagedThreadId);
            }
        }
    }
}

L'exécution se déroulera quelque chose comme ça

Process Lock Reached: 15
Process Lock Enter: 15
DoWork Lock Reached: 12
Process Lock Reached: 17
DoWork Lock Reached: 11
DoWork Lock Reached: 10
DoWork Lock Reached: 13
DoWork Lock Reached: 9
Process Lock Reached: 18
Process Lock Reached: 14
Process Lock Reached: 16
Process Lock Exit: 15
Thread Lock Enter: 9
Thread Lock Exit: 9
Process Lock Enter: 14
Process Lock Exit: 14
Thread Lock Enter: 10
Thread Lock Exit: 10
Thread Lock Enter: 11
Thread Lock Exit: 11
Process Lock Enter: 16
Process Lock Exit: 16
Thread Lock Enter: 12
Thread Lock Exit: 12
Process Lock Enter: 17
Process Lock Exit: 17
Thread Lock Enter: 13
Thread Lock Exit: 13
Process Lock Enter: 18
Process Lock Exit: 18

Comme vous pouvez le voir, le processus d'atteindre le verrou est différent de l'entrée dans le verrou.

0voto

Franci Penov Points 45358

Windows et le CLR font de leur mieux pour garantir l'équité (l'ordre FIFO) de l'attente. Cependant, il existe certains scénarios où l'ordre des threads en attente sur un verrou peut être modifié, principalement autour des attentes alertables et tout verrouillage de thread CLR met le thread dans un état alertable.

Pour toutes fins pratiques, vous pouvez supposer que l'ordre sera FIFO; cependant, soyez conscient de ce problème.

-1voto

marcc Points 8513

Il va attendre, et ils NE seront pas dans le même ordre.

En fonction de vos besoins, vous pourriez obtenir de meilleures performances si vous regardez quelque chose comme un ReaderWriterLock ou autre chose que simplement lock

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X