Являются ли методы SetValue/GetValue в System.Array потокобезопасными?

У нас была небольшая дискуссия в офисе, и документально подтвержденного ответа не последовало:

Является ли System.Array.SetValue потокобезопасным?

using System;
using System.Text;
using System.Threading;

namespace MyApp
{
    class Program
    {
        private static readonly object[] arr = new object[3];

        static void Main(string[] args)
        {
            string value1 = "hello";
            int value2 = 123;
            StringBuilder value3 = new StringBuilder();
            value3.Append("this"); 
            value3.Append(" is "); 
            value3.Append("from the StringBuilder");

            var states = new object[]
                             {
                                 new object[] {0, value1},
                                 new object[] {1, value2},
                                 new object[] {2, value3}
                             };

            ThreadPool.QueueUserWorkItem(MySetValue, states[0]);
            ThreadPool.QueueUserWorkItem(MySetValue, states[1]);
            ThreadPool.QueueUserWorkItem(MySetValue, states[2]);
            Thread.Sleep(0);

            Console.WriteLine("press enter to continue");
            Console.ReadLine();

            // print the result
            Console.WriteLine("result:");
            for (int i = 0; i < arr.Length; i++)
            {
                Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
            }

            // quit
            Console.WriteLine("press enter to quit");
            Console.ReadLine();

        }

        // callback
        private static void MySetValue(object state)
        {
            var args = (object[]) state;
            var index = (int)args[0];
            var value = args[1];
            arr[index] = value; // THREAD-SAFE ??
        }
    }
}

Как видите, каждый поток устанавливает свой уникальный элемент в статическом массиве. Я внимательно изучил код с помощью рефлектора (и заглянул в mscorlib.pdb). В конце концов звучит призыв:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private unsafe extern static void InternalSetValue(void * target, Object value); 

Что не задокументировано. Взгляните на документацию MSDN по System.Array в целом и в SetValue(object, int), в частности. Ничего о потокобезопасности (или, может быть, Я что-то упускаю).

Как это выражено в ответе Джона Скита на похожий вопрос:

Я считаю, что если каждый поток будет работать только с отдельной частью массива, все будет хорошо.

Я пытаюсь получить точный ответ GetValue(int) и SetValue(object, int) по этому вопросу. У кого-нибудь есть ссылка на документацию и/или лучшее понимание InternalSetValue?


person Ron Klein    schedule 09.12.2009    source источник
comment
Эрик Липперт хорошо прочитал о том, что на самом деле означает потокобезопасность: blogs.msdn.com/ericlippert/archive/2009/10/19/   -  person Dirk Vollmar    schedule 09.12.2009
comment
В вашем примере метод SetValue не будет вызываться, вместо этого компилятор будет выдавать код операции Stelem, а не код операции вызова.   -  person Pop Catalin    schedule 09.12.2009


Ответы (3)


MSDN: класс массива

Общедоступные статические (общие в Visual Basic) члены этого типа являются потокобезопасными. Любые члены экземпляра не гарантируют потокобезопасность.

Эта реализация не предоставляет синхронизированную (потокобезопасную) оболочку для массива; однако классы .NET Framework, основанные на Array, предоставляют собственную синхронизированную версию коллекции с помощью свойства SyncRoot.

Это не потокобезопасно!

Редактировать:

Некоторая дополнительная информация: метод SetValue в классе Array не вызывается в обычных обстоятельствах, он вызывается только тогда, когда массив используется через интерфейс IList.

следующий код:

int[] arr = ...
arr[i] = value;

Не будет генерировать вызов SetValue(), вместо этого будет сгенерирован код операции OpCodes.Stelem.

Таким образом, довольно неважно, является ли метод SetValue потокобезопасным или нет, если только доступ к массиву не осуществляется с использованием ссылки IList.

person Pop Catalin    schedule 09.12.2009
comment
Я действительно не знаю, как я пропустил это. Спасибо! - person Ron Klein; 09.12.2009
comment
Так что в целом это не потокобезопасно, но как насчет конкретного сценария, когда каждый поток обращается только к отдельной части массива? - person Dirk Vollmar; 09.12.2009
comment
@divo, его потокобезопасность не гарантируется, то есть даже если текущая реализация является деталью реализации. - person Pop Catalin; 09.12.2009

В вашем примере, где каждый поток устанавливает другой элемент в массиве, он будет потокобезопасным. Видите это как холодильник, там три банки пива, трое разных подходят к холодильнику и берут его банку пива, все пройдет хорошо, выпьют наполовину, поставят обратно и вернутся за ним позже.

Однако вы не можете разделить 1 банку пива с тремя людьми ни при программировании, ни в реальной жизни.

так что да:

если каждый поток работает только с отдельной частью массива, все будет хорошо

PS: у меня нет источника, чтобы проверить это, но я постоянно использую потоки, и у меня никогда не было проблемы с голоданием (?), Когда каждый поток читает и записывает одновременно в массив. Я ДЕЙСТВИТЕЛЬНО страдаю от проблем, когда делюсь «одной и той же банкой пива», поэтому я считаю, что мой ответ правильный, но я хотел бы, чтобы кто-нибудь подтвердил это.

person Pieter888    schedule 09.12.2009
comment
Как вы можете сказать? Может быть, класс System.Array генерирует свой собственный кеш при каждом вызове установщика, и этот процесс не является потокобезопасным? - person Ron Klein; 09.12.2009

В вашем примере вызовы InternalSetValue(void *, object) выполняются в трех разных местах памяти. Следовательно, он должен быть потокобезопасным. Записи в эти места не передаются в другие места, даже если они являются членами одного и того же массива.

person Jeffrey L Whitledge    schedule 09.12.2009
comment
Действительно, пример кода вызывает InternalSetValue три раза с тремя разными ячейками памяти, но это, насколько я могу судить. Я не знаю, какова реализация InternalSetValue, и поэтому я не могу быть уверен в его потокобезопасности. - person Ron Klein; 09.12.2009