Многопоточность, лямбды и локальные переменные - PullRequest
7 голосов
/ 05 декабря 2011

Мой вопрос в приведенном ниже коде: могу ли я быть уверен, что методы экземпляра будут обращаться к переменным, я думаю, что они получат, или они могут быть изменены другим потоком, пока я все еще работаю? Связаны ли замыкания с этим, то есть я буду работать с локальной копией IEnumerable<T>, чтобы перечисление было безопасным?

Если перефразировать мой вопрос, нужны ли мне какие-либо блокировки, если я никогда не пишу в общие переменные?

public class CustomerClass
{
    private Config cfg = (Config)ConfigurationManager.GetSection("Customer");

    public void Run()
    {
        var serviceGroups = this.cfg.ServiceDeskGroups.Select(n => n.Group).ToList();

        var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID"));
        Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>(
            groupedData,
            () => new CustomerDataContext(),
            (g, _, ctx) =>
            {
                var inter = this.FindOrCreateInteraction(ctx, g.Key);

                inter.ID = g.Key;
                inter.Title = g.First().Field<string>("Title");

                this.CalculateSomeProperty(ref inter, serviceGroups);

                return ctx;
            },
            ctx => ctx.SubmitAllChanges());
    }

    private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID)
    {
        var inter = ctx.Interactions.Where(n => n.Id = ID).SingleOrDefault();

        if (inter == null)
        {
            inter = new Interaction();
            ctx.InsertOnSubmit(inter);
        }

        return inter;
    }

    private void CalculateSomeProperty(ref Interaction inter, IEnumerable<string> serviceDeskGroups)
    {
        // Reads from the List<T> class instance variable. Changes the state of the ref'd object.
        if (serviceGroups.Contains(inter.Group))
        {
            inter.Ours = true;
        }
    }
}

Ответы [ 2 ]

3 голосов
/ 07 декабря 2011

Кажется, я нашел ответ и в процессе тоже вопрос.

Реальный вопрос заключался в том, можно ли доверять локальным «переменным», которые на самом деле являются объектами, для одновременного доступа. Ответ - нет, если они имеют внутреннее состояние, которое не обрабатывается потокобезопасным способом, все ставки отключены. Закрытие не помогает, оно просто захватывает ссылку на указанный объект.

В моем конкретном случае - одновременное чтение из IEnumerable<T> и отсутствие записи в него, является на самом деле потокобезопасным, поскольку каждый вызов foreach, Contains(), Where() и т. Д. получает новый IEnumerator, который виден только из потока, который его запросил. Любые другие объекты, однако, также должны быть проверены, один за другим.

Итак, ура, никаких блокировок или синхронизированных коллекций для меня:)

Благодаря @ebb и @Dave, хотя вы не ответили на вопрос напрямую, вы указали мне правильное направление.


Если вас интересуют результаты, это запуск на моем домашнем ПК (четырехъядерный процессор) с Thread.SpinWait для имитации времени обработки строки. Реальное приложение улучшилось почти в 2 раза (01:03 против 00:34) на двухъядерной гиперпоточной машине с SQL Server в локальной сети.

Singlethreaded Однопоточный, с использованием foreach. Я не знаю почему, но количество переключателей контекста ядра довольно велико.

Multithreaded Использование Parallel.ForEach, без блокировки с локальными потоками, где это необходимо.

1 голос
/ 06 декабря 2011

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

Если бы в этих методах экземпляра использовалась переменная-член, то они были бы такими же поточно-безопасными, как и эта переменная (например, если вы использовали простой список, он не был бы потокобезопасным, и вы можете увидеть странное поведение),Короче говоря, переменные-члены являются врагом легкой безопасности потоков.

Вот мой рефакторинг (отказ от ответственности, не тестировался).Если вы хотите предоставить передаваемые данные, вы останетесь в здравом уме, если передадите их как параметры и не сохраните их как переменные-члены:

ОБНОВЛЕНИЕ: вы попросили указать ссылку на список только для чтения.поэтому я добавил это и удалил статические теги (чтобы можно было совместно использовать переменную экземпляра).

public class CustomerClass
{

private List<string> someReadOnlyList;

    public CustomerClass(){
      List<string> tempList  = new List<string>() { "string1", "string2" };
      someReadOnlyList = ArrayList.Synchronized(tempList);
    }

    public void Run()
    {
        var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID"));

        Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>(
            groupedData,
            () => new CustomerDataContext(),
            (g, _, ctx) =>
            {
                var inter = FindOrCreateInteraction(ctx, g.Key);

                inter.ID = g.Key;
                inter.Title = g.First().Field<string>("Title");

                CalculateSomeProperty(ref inter);

                return ctx;
            },
            ctx => ctx.SubmitAllChanges());
    }

    private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID)
    {
        var query = ctx.Interactions.Where(n => n.Id = ID);

        if (query.Any())
        {
            return query.Single();
        }
        else
        {
            var inter = new Interaction();
            ctx.InsertOnSubmit(inter);
            return inter;
        }
    }

    private void CalculateSomeProperty(ref Interaction inter)
    {
        Console.Writeline(someReadOnlyList[0]);
        //do some other stuff
    }
}
...