Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СПО - задание по лабам.doc
Скачиваний:
4
Добавлен:
17.11.2019
Размер:
113.15 Кб
Скачать

7. Удаление повторяющихся чисел

Имеется неупорядоченный набор чисел. Предполагается, что все значения чисел в наборе различны. Требуется проверить справедливость данного утверждения и, если оно не справедливо, удалить из набора повторяющиеся числа.

8. Проверка транзитивности

Задан набор пар натуральных чисел, причем пары (a, b) и (b, a) считаются различными. Проверить, выполняется ли для вхождения чисел в данный набор свойство транзитивности: если набор содержит пары (a, b) и (b, c), то он обязательно содержит и пару (a, c).

Лабораторная работа № 2 Разделение ресурсов

Цель работы – приобретение навыков организации доступа нескольких параллельных процессов к общим ресурсам.

Порядок выполнения работы

  1. Ознакомиться с методическими указаниями.

  2. Подготовить параллельные алгоритмы решения задачи приведенной в конце описания (вариант определяется номером бригады). Согласовать формализацию с преподавателем (содержание формализации см. ниже). При решении использовать:

  1. семафор (в качестве семафоров использовать http://www.imasy.or.jp/~fukumoto/ruby/semaphore.rb);

  2. монитор.

  1. Реализовать алгоритмы на языке Ruby.

Содержание отчета

1. Постановка задачи

2. Формализация решения

2.1. Типы потоков и их количество

2.2. Условия ожидания потоков каждого типа

2.3. Назначение и число семафоров

2.4. Методы монитора (название, назначение, параметры).

2.5. Указать классическую задачу, на которую похожа данная.

2.6. Случайные задержки, используемые программе.

3. Сопоставление эффективности мониторов и семафоров.

4. Выводы

5. Приложения

5.1. Результаты тестирования

5.2. Листинги программ

Методические указания

Процессы называются параллельными, если они существуют одновременно. Среди процессов принято выделять потоки – это процессы, имеющие общую область памяти (разделяемые переменные). Создание нескольких потоков управления реализуется либо с помощью обращения к операционной системе, либо с помощью средств системы программирования.

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

  • неудовлетворительная скорость работы последовательного алгоритма, при наличии многопроцессорной системы;

  • исходная задача лучше формализуется в виде набора параллельных процессов.

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

Семафор представляет собой защищенную переменную, т.е. из программы пользователя над семафором нельзя совершать обычные арифметические операции или присваивать ему какое-либо значение. Допустима команда инициализации семафора целым неотрицательным числом. Доступ к нему разрешен только через операции P(S), V(S):

P(S) = IF S > 0 THEN S := S - 1

ELSE ждать операции V(s);

V(S) = IF есть ждущие процессы на S THEN разрешить одному из процес­сов продолжить выполнение

ELSE S := S + 1;

Выполнить любую из данных операций над конкретным семафором в один момент времени может только один процесс, т.е. выполняется правило взаимного исключения. Обычно процессы, ждущие на семафоре, организованы согласно дисциплине FIFO [1].

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

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

  • обеспечить взаимное исключение при доступе к разделяемым ресурсам;

  • не допускать взаимной блокировки процессов, т.е. ситуации, когда ни один из процессов не может продолжить свою работу;

  • избегать режима активного ожидания;

  • избегать ситуаций бесконечного откладывания момента входа процессов в их критические участки;

  • помещать в область взаимного исключения минимально необходимое число операторов.

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

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

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

Монитор, динамически выделяющий ресурс, иногда должен задерживать процесс, желающий получить ресурс, который в настоящее время недоступен, и возобновлять этот процесс после того, как другой процесс освободит данный ресурс. Для этого существуют операции, вызываемые в процедуре монитора:

  • операция ожидания - блокирует вызывающий процесс;

  • операция сигнализации - заставляет один из ожидающих процессов возобновиться.

Если нет ожидающих процессов, операция сигнализации не имеет никакого эффекта. Чтобы давать возможность другим программам освободить ресурсы в течение ожидания, операция ожидания должна оставить возможность прерывания, иначе это предотвратило бы вход в процедуру освобождения.

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

Часто, может быть более одной причины для ожидания. Поэтому в мониторе существует особый тип переменной, называемый "условием" и тот, кто пишет монитор, должен объявить переменную типа условия для каждой причины, по которой процессу, возможно, придется ждать. Тогда операции ожидания и сигнализации должны в качестве аргумента получать переменную условия.

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

Примеры решения классических задач (постановка задач см. лекции)

Производители и потребители

require 'semaphore'

t0 = Time.now

buf = Array.new(5)

tp = []

tc = []

c = 20

s1 = Semaphore.new(buf.size)

s2 = Semaphore.new(0)

m = Semaphore.new(1)

producer = Thread.new do

i = 0

c.times do

d = rand(10)

tp << d

puts "produce #{d} i=#{i}"

s1.P

m.P

buf[i] = d

m.V

s2.V

i = ( i + 1 ) % buf.size

end

end

consumer = Thread.new do

i = 0

c.times do

s2.P

m.P

d = buf[i]

m.V

s1.V

tc << d

puts "consume #{d} i=#{i}"

i = ( i + 1 ) % buf.size

end

end

producer.join

consumer.join

puts tp.join(',')

puts tc.join(',')

puts tp == tc

puts Time.now - t0

Писатели и читатели

require 'monitor'

class RWControl

include MonitorMixin

def initialize

@can_read = new_cond

@can_write = new_cond

@num_readers = 0

@is_writing = false

super()

end

def begin_reading

synchronize do

@can_read.wait if @is_writing or @can_write.count_waiters > 0

@num_readers += 1

@can_read.signal

end

end

def end_reading

synchronize do

@num_readers -= 1

@can_write.signal if @num_readers == 0

end

end

def begin_writing

synchronize do

@can_write.wait if @is_writing or @num_readers > 0

@is_writing = true

end

end

def end_writing

synchronize do

@is_writing = false

if @can_read.count_waiters > 0

@can_read.signal

else

@can_write.signal

end

end

end

end

m = RWControl.new

50.times do

Thread.new do

sleep rand * 2

m.begin_reading

puts Thread.current.to_s + ' reading'

sleep rand

m.end_reading

end

end

10.times do

Thread.new do

sleep rand * 2

m.begin_writing

puts Thread.current.to_s + ' writing'

sleep rand * 2

m.end_writing

end

end

Thread.list.each { |t| t.join unless t == Thread.main }

Философы

require 'semaphore'

N = 5

class Table

def initialize( n )

@forks = Array.new( n ) { Semaphore.new(1) }

@m = Semaphore.new( n - 1 )

end

def take_left(i)

@m.P

@forks[ i - 1 ].P

end

def take_right(i)

@forks[ i ].P

end

def put_left(i)

@m.V

@forks[ i - 1 ].V

end

def put_right(i)

@forks[ i ].V

end

end

table = Table.new(N)

N.times do |i|

Thread.new(i) do |k|

loop do

puts "thinking #{k}"; sleep rand

table.take_left(k); sleep 2*rand

table.take_right(k)

puts "#{k} eating"; sleep rand

table.put_left(k)

table.put_right(k)

end

end

end

Thread.list.each{ |t| t.join if t != Thread.current }

require 'monitor'

class Table

include MonitorMixin

def initialize( n )

super()

@c = Array.new( n ) { new_cond }

@forks = Array.new( n ) { 2 }

end

def take_both(i)

synchronize do

@c[i].wait if @forks[i] < 2

@forks[i-1] -= 1

@forks[(i+1)%@c.size] -= 1

end

end

def put_both(i)

synchronize do

@forks[i-1] += 1

@forks[(i+1)%@c.size] += 1

@c[i-1].signal if @forks[i-1] == 2

@c[(i+1)%@c.size].signal if @forks[(i+1)%@c.size] == 2

end

end

end

table = Table.new(N)

N.times do |i|

Thread.new(i) do |k|

loop do

puts "thinking #{k}"; sleep rand

table.take_both(k);

puts "#{k} eating"; sleep rand

table.put_both(k)

end

end

end

Thread.list.each{ |t| t.join if t != Thread.current }