Возможна утечка памяти в ConcurrentBag? - PullRequest
8 голосов
/ 18 марта 2011

Я читал о новых параллельных коллекциях, и особенно ConcurrentBag привлек мое внимание.Поскольку ConcurrentBag внутренне содержит локальный набор в каждом отдельном потоке, используя его для отслеживания элементов, это означает, что когда сам поток выходит из области видимости, он все равно будет ссылаться в памяти ConcurrentBag.Это в свою очередь означает как память, заявленную потоком, так и собственные ресурсы?(извините, что не знаете точную внутреннюю работу объекта потока .NET)

Я могу предположить, что у вас есть 1 глобальный ConcurrentBack для многопоточного веб-сервиса, где у вас много клиентов, добавляющих задачи.Эти задачи добавляются потоками в пул потоков.Теперь пул потоков - очень эффективный способ управления потоками, но он действительно удаляет и создает потоки в зависимости от объема работы.Поэтому такой веб-сервис иногда может оказаться в затруднительном положении, поскольку основная сумка все еще ссылается на многие подлежащие уничтожению потоки.

Я создал быстрое приложение для проверки этого поведения:

    static ConcurrentBag<int> bag = new ConcurrentBag<int>();
    static void FillBag() { for (int i = 0; i < 100; i++) { bag.Add(i); } }
    static void PrintState() { Console.WriteLine("Bag size is: {0}", bag.Count); }
    static void Main(string[] args)
    {
        var remote = new Thread(x =>
        {
            FillBag();
            PrintState();
        });
        // empty bag
        PrintState();
        // first 100 items are added on main thread
        FillBag();
        PrintState();
        // second 100 items are added on remote thread
        remote.Start();
        remote.Join();
        // since the remote thread is gone out of scope, what happened to its local storage which is part of the bag?
        PrintState();
        // now force a cleanup
        WeakReference weakRemoteReference = new WeakReference(remote); 
        remote = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // Now check if the thread still exists
        if (weakRemoteReference.IsAlive)
            Console.WriteLine("Remote thread still exists");
        PrintState();
        Console.ReadLine();

И вывод подтверждает мою историю:

Bag size is: 0
Bag size is: 100
Bag size is: 200
Bag size is: 200
Remote thread still exists
Bag size is: 200

Можно ли ожидать такого поведения, допустил ли я ошибку в своем тесте или это можно считать недостатком дизайна?

1 Ответ

8 голосов
/ 18 марта 2011

ConcurrentBag действительно хранит данные в локальном хранилище потоков, и если вы откажетесь от потоков, может вызвать утечку памяти. Однако реализация может «украсть» элементы из списка одного потока, чтобы передать другому потоку. Вы можете увидеть это в действии, если напишите следующее:

ConcurrentBag<int> MyBag = new ConcurrentBag<int>();

void DoIt()
{
    for (int i = 0; i < 10; ++i)
    {
        MyBag.Add(i);
    }

    ThreadPool.QueueUserWorkItem(EmptyBag);

    Console.Write("Press Enter:");
    Console.ReadLine();

    Console.WriteLine("{0} items in bag", MyBag.Count);
}

void EmptyBag(object state)
{
    int take;
    while (MyBag.TryTake(out take))
    {
        Console.WriteLine(take);
    }
    Console.WriteLine("Bag is empty");
}

Если вы запустите эту программу и подождите, пока не появится сообщение «Bag is empty», прежде чем нажать Enter, вы увидите, что мешок действительно опустошен.

Так что, пока из сумки будет прочитана одна нить, она будет в конце концов опустошена. Даже если все элементы были добавлены другими темами.

Так что, да, возможна утечка памяти. На практике, тем не менее, если к пакету обращаются несколько нитей, это, вероятно, не является проблемой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...