Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1158 / 192 | Длительность: 12:09:00
Лекция 4:

Синхронизация потоков

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Аннотация: В рамках данной лекции будут рассмотрены следующие вопросы: оператор lock; классы синхронизации в .NET Framework; Interlocked; класс Monitor; методы класса Monitor: Wait, Pulse и PulseAll; класс Mutex; класс Semaphore; класс Barrier; класс ReaderWriterLockSlim; события синхронизации потоков. При использовании в программе нескольких потоков иногда необходимо координировать их выполнение. Процесс координации потоков называется синхронизацией. К синхронизации прибегают в тех случаях, когда двум или большему числу потоков необходимо получить доступ к общему ресурсу, который в каждый момент времени может использовать только один поток. Синхронизацию потоков можно осуществлять несколькими способами описанных в данной лекции.

Оператор lock

Оператор lock предназначен для того, чтобы одному потоку не дать войти в важный раздел кода в тот момент, когда в нем находится другой поток. При попытке входа другого потока в заблокированный код потребуется дождаться снятия блокировки объекта. Этот оператор оформляется следующим образом:

Object thisLock = new Object();
lock (thisLock)
{
    // Критический фрагмент код
}

где thisLock - обозначает ссылку на синхронизируемый объект, который гарантирует, что фрагмент кода, защищенный блокировкой для данного объекта, будет использоваться только в потоке, получающем эту блокировку, а все остальные потоки блокируются до тех пор, пока блокировка не будет снята. Блокировка снимается по завершении защищаемого ею фрагмента кода.

Так же для блокировки объектов можно применять конструкция lock (this). Но данную конструкцию следует применять в том случае, если this является ссылкой на закрытый объект.

Пример работы распараллеленного приложения без синхронизации потоков:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace OperatorLock
{
    class Work
    {
        int i, j;
        public void ThreadStart()
        {
        
                if (i != j)
                    Console.WriteLine("Ошибка");
                i++;
              
                Thread.Sleep(200);
                j++;
                Console.WriteLine("{0},{1}", i, j);
                 }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Work store = new Work();
         
                for (int i = 0; i < 10; ++i)
                {
                    new Thread(new ThreadStart(store.ThreadStart)).Start();
                    Thread.Sleep(100);
                 }   
            Console.ReadLine();
        }
    }
}

В методе Main() - метод ThreadStar() 10 раз, в отдельных потоках. Запустим программу. В случайном порядке выведется на экран цифры и надпись "ошибка" (Рис. 5.1).

 Результат выполнения программы без оператора lock

увеличить изображение
Рис. 5.1. Результат выполнения программы без оператора lock

Такой результат получается потому, что приращение значения переменной i и переменой j происходит не сразу, а с небольшой задержкой (в данном случае искусственной, полученной с помощью метода Sleep(), который усыпляет поток на 200 милисекунд). Именно на этом моменте происходит рассинхронизация потоков. Для синхронизации потоков используется оператор блокировки lock. Модифицируем пример, добавив в него синхронизацию:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace OperatorLock
{
    class Work
    {
        int i, j;
        object LockObj = new object();
        public void ThreadStart()
        {
            lock (LockObj)
            {
                if (i != j)
                    Console.WriteLine("Ошибка");
                i++;
                Thread.Sleep(200);
                j++;
                Console.WriteLine("{0},{1}", i, j);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Work store = new Work();
         
                for (int i = 0; i < 10; ++i)
                {
                    new Thread(new ThreadStart(store.ThreadStart)).Start();
                    Thread.Sleep(100);
                }
          
            Console.ReadLine();
        }
    }
}

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

 Результат выполнения программы  с оператором lock

увеличить изображение
Рис. 5.2. Результат выполнения программы с оператором lock

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.