Вы находитесь на странице: 1из 11

Лабораторная работа N2. Межпроцессный обмен.

Содержание работы: Обмен данными между потоками.


Первый поток выбирает одну из двух команд, согласно
индивидуальному заданию: одна по работе со строками, другая по работе с
числами. Команды состоят из названия команды или ее номера и
сопроводительной информации (строки, количества элементов массива,
элементов массива).
Второй поток принимает команды и информацию и выполняет
команды и посылает первом сообщение о причине невыполнения команды
или результат.
Данные между двумя потоками передаются с помощью одного из
механизмов межпроцессного обмена. Для синхронизации доступа к общему
ресурсу используется одно из средств синхронизации.
Работа с потоками, средствами межпроцессного обмена и средствами
синхронизации доступа выполняется путем выполнения программы на
языке C# .
Схема обмена данными между процессами
Средство обмена

Процесс 2
Процесс 1 (нить 2)
(нить 1)

Индивидуальные задания
Вариант задания имеет вид:
SFOX, где
S – команда обработки строк (1 - 10):
1. Объединение двух строк
2. Подсчет длины текста
3. Подсчет средней длины слов строки (строка разделяется на слова
с помощью звездочки)
4. Подсчет количества слов из цифр (строка разделяется на слова с
помощью двоеточия)
5. Подсчет суммы отрицательных чисел – слов строки (строка
разделяется на слова с помощью запятой)
1
6. Подсчет числа 5-символьных слов из латинских букв в строке
(строка разделяется на слова с помощью запятой)
7. Выделение подстроки заданной длины с начала строки
8. Подсчет количества указанного символа в строке
9. Сравнение двух строк и выдача количества несовпадающих
символов
10. Вставка одной строки в другую с заданного символа
F – команда обработки массива (1 - 6):
1. Подсчет количества отрицательных элементов в массиве
2. Подсчет количества неотрицательных элементов в массиве
3. Подсчет суммы положительных чисел массива
4. Подсчет числа положительных элементов с четными индексами
5. Замена отрицательных элементов массива их квадратами
6. Удаление из массива четных элементов
O - вариант обмена данными между потоками (1-3)
1. неименованный канал
2. именованный канал
3. файл
X - вариант синхронизации использования ресурсов (1-3)
4. lock
5. mutex
6. monitor

Средства межпроцессного взаимодействия

Каналы представляют собой средство межпроцессного взаимодействия.


Существует два вида каналов.

 Анонимные каналы.

Анонимные каналы обеспечивают межпроцессное взаимодействие на


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

В .NET Framework анонимные каналы реализуются с помощью классов


AnonymousPipeServerStream и AnonymousPipeClientStream.

2
AnonymousPipeServerStream предоставляет доступ в одном направлении (In
или Out) и инициирует впоследствии доступ к другому классу
AnonymousPipeClientStream.
Канал создается с помощью вызова
AnonymousPipeServerStream(PipeDirection direction)
Затем этот канал переназначается на поток ввода или вывода (StreamReader
или StreamWriter), из которого читается/в который пишется информация.
AnonymousPipeClientStream предоставляет доступ в одном направлении (In
или Out), зная дескриптор канала, созданного серверным потоком.
Дескриптор канала можно получить с помощью функции серверного канала
GetClientHandleAsString().
Канал создается с помощью вызова
AnonymousPipeClientStream(PipeDirection напрравление,SafePipeHandle
дескриптор канала, созданного сервером)

Именованные каналы.

Именованные каналы обеспечивают межпроцессное взаимодействие между


сервером и одним или несколькими клиентами. Именованные каналы могут
быть односторонними или двусторонними. Они поддерживают связь с
помощью сообщений и позволяют нескольким клиентам подключаться к
серверному процессу одновременно, используя одно и то же имя канала.
В .NET Framework именованные каналы реализуются с помощью классов
NamedPipeServerStream и NamedPipeClientStream.

NamedPipeServerStream предоставляет доступ в k.,jv одном направлении (In


или Out) и инициирует впоследствии доступ к другому классу
AnonymousPipeClientStream.
Канал создается с помощью вызова
NamedPipeServerStream(PipeDirection direction)
Затем этот канал переназначается на поток ввода или вывода (StreamReader
или StreamWriter), из которого читается/в который пишется информация.
WaitForConnection ожидает подключения клиента к объекту
NamedPipeServerStream
.
NamedPipeClientStream предоставляет доступ в любом направлении (In или
Out).
Канал создается с помощью вызова
Инициализирует новый экземпляр класса NamedPipeClientStream с
заданными именами канала и сервера и указанным направлением канала.
NamedPipeClientStream(string имя сервера,string имя канала,PipeDirection
направление)
Имя сервера - Имя удаленного компьютера, к которому нужно
подключиться, или значение ".", чтобы указать локальный компьютер.

3
Для подключения к серверу NamedPipeClientStream должен выполнить
метод Connect()

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

При использовании в программе нескольких потоков иногда необходимо


координировать их выполнение. Процесс координации потоков называется
синхронизацией. К синхронизации прибегают в тех случаях, когда двум или
большему числу потоков необходимо получить доступ к общему ресурсу,
который в каждый момент времени может использовать только один поток.
Синхронизацию потоков можно осуществлять несколькими способами

4
Оператор lock

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

Object thisLock = new Object();


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

где thisLock - обозначает ссылку на синхронизируемый объект, который


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

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


потоков:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace OperatorLock
{class Program
{
public static int i, j;
public static void ThreadStart()
{

if (i != j)
Console.WriteLine("Ошибка");
i++;

Thread.Sleep(200);
j++;
Console.WriteLine("{0},{1}", i, j);

5
static void Main(string[] args)
{
for (int i = 0; i < 10; ++i)
{ Thread th1 = new Thread(() => ThreadStart());
Th1.Start();
Thread.Sleep(100);

Console.ReadLine();
}
}
}

В методе Main() функция ThreadStar() выполняется 10 раз в отдельных


потоках. Запустим программу. В случайном порядке выведется на экран
цифры и надпись "ошибка"

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

Такой результат получается потому, что приращение значения переменной i


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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
6
using System.Threading;
namespace OperatorLock
{ class Program
{ public static 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);
}
}

static void Main(string[] args)


{

for (int i = 0; i < 10; ++i)


{ Thread th1 = new Thread(() => ThreadStart());
Th1.Start();
Thread.Sleep(100);
}

Console.ReadLine();
}
}
}

Как только поток войдет в контекст lock, маркер блокировки (в данном


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

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

Класс Monitor
Класс Monitor предназначен для того, чтобы контролировать доступ к
объектам, предоставляя блокировку объекта одному потоку. Блокировки
объектов предоставляют возможность ограничения доступа к части кода,
обычно называемой критической секцией. Пока поток владеет блокировкой
для объекта, никакой другой поток не может ею завладеть.
В классе Monitor определено несколько методов синхронизации. Например,
чтобы получить возможность блокировки для некоторого объекта,
вызывается метод Enter(), а чтобы снять блокировку - метод Exit(). Эти
методы имеют следующий формат:
public static void Enter(object syncOb)
public static void Exit(object syncOb)
где syncOb - синхронизируемый объект. Если при вызове метода Enter()
заданный объект недоступен, вызывающий поток будет ожидать до тех пор,
пока объект не станет доступным.
Использование оператора lock эквивалентно вызову метода Enter() с
последующим вызовом метода Exit() класса Monitor.

8
Класс Mutex

Когда двум или более потокам одновременно требуется доступ к общему


ресурсу, системе необходим механизм синхронизации, чтобы обеспечить
использование ресурса только одним потоком одновременно. Класс Mutex,
определенный в пространстве имен System. Threading - это примитив,
который предоставляет эксклюзивный доступ к общему ресурсу только
одному потоку синхронизации. Если поток получает семафор, второй поток,
желающий получить этот семафор, приостанавливается до тех пор, пока
первый поток не освободит семафор. Термин Mutex происходит от фразы
mutually exclusive (взаимно исключающий), и поскольку только один поток
может получить блокировку монитора для данного объекта в любой момент
времени, только один поток в любой момент времени может получить данный
Mutex. Класс Mutex очень похож на класс Monitor тем, что тоже допускает
наличие только одного владельца. Только один поток может получить
блокировку и иметь доступ к защищаемым Mutex синхронизированным
областям кода. У Mutex имеется несколько конструкторов. Ниже приведены
три наиболее употребительных конструктора:

public Mutex()
public Mutex(bool initiallyOwned);
public Mutex(bool initiallyOwned, string name_mutex)

В первой форме конструктора создается Mutex, которым первоначально никто


не владеет. А во второй и третей форме исходным состоянием Mutex
завладевает вызывающий поток, если параметр initiallyOwned имеет
логическое значение true, если false, то объектом Mutex никто не владеет.

Для того чтобы получить Mutex, используется метод WaitOne(). Метод


WaitOne() ожидает до тех пор, пока не будет получен Mutex, для которого он
был вызван. Следовательно, этот метод блокирует выполнение вызывающего
потока до тех пор, пока не станет доступным указанный Mutex. Данный метод
всегда возвращает логическое значение true. Форма объявления метода
WaitOne():

Mutex mutex = new Mutex(false);


mutex.WaitOne();

Когда в коде не требуется использовать Mutex, он освобождается с помощью


метода ReleaseMutex():

mutex.ReleaseMutex();

Ниже продемонстрирован код реализующий работу Mutex:

9
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Mutext
{
class Program
{
private static Mutex mut = new Mutex();
private const int numIterations = 1;
private const int numThreads = 3;
private static void Main(string[] args)
{
// Создаем потоки, которые будут использовать защищенный ресурс
for (int i = 0; i < numThreads; i++)
{
Thread myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = String.Format("Поток{0}", i + 1);
myThread.Start();
}
// Главный поток завершил работу
}
private static void MyThreadProc()
{
for (int i = 0; i < numIterations; i++)
{
UseResource();
}
}
//Синхронизируем данный метод
private static void UseResource()
{
mut.WaitOne();
Console.WriteLine("{0} зашел в защищенную зону",
Thread.CurrentThread.Name);
// Имитируем работу
Thread.Sleep(500);
Console.WriteLine("{0} покинул защищенную зону",
Thread.CurrentThread.Name);
// Release the Mutex.
mut.ReleaseMutex();
Console.ReadLine();
}

10
}}

Результат работы программы, с использованием Mutex.

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

Если же закомментировать методы WaitOne() и ReleaseMutex() и запустить


программу, то программа сгенерирует результат.

Результат выполнения программы без использования синхронизации Mutex

11

Вам также может понравиться