Алгоритмы синхронизации






Скачать 223.76 Kb.
НазваниеАлгоритмы синхронизации
Дата публикации25.01.2015
Размер223.76 Kb.
ТипДокументы
top-bal.ru > Физика > Документы

Алгоритмы синхронизации



Для корректного взаимодействия процессов недостаточно одних организационных усилий операционной сис­темы. Необходимы определенные внутренние изменения в поведении процессов. В настоящей лекции рас­сматриваются вопросы, связанные с такими изменениями, приводятся программные алгоритмы корректной организации взаимодействия процессов.

В предыдущей лекции мы говорили о внешних проблемах кооперации, связанных с ор­ганизацией взаимодействия процессов со стороны операционной системы. Предполо­жим, что надежная связь процессов организована, и они умеют обмениваться информа­цией. Нужно ли нам предпринимать еще какие-либо действия для организации пра­вильного решения задачи взаимодействующими процессами? Нужно ли изменять их внутреннее поведение? Разъяснению этих вопросов и посвящена данная лекция.
^

Interleaving, race condition и взаимоисключения


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

  1. Отрезать ломтик хлеба.

  2. Отрезать ломтик колбасы.

  3. Намазать ломтик хлеба маслом.

  4. Положить ломтик колбасы на подготовленный ломтик хлеба.

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

Пусть имеется две активности

P: a b c

Q: d e f

где a, b, c, d, e, f – атомарные операции. При последовательном выполнении ак­тивностей мы получаем такую последовательность атомарных действий:

PQ: a b c d e f

Что произойдет при исполнении этих активностей псевдопараллельно, в режиме разде­ления времени? Активности могут расслоиться на неделимые операции с различным чередованием, то есть может произойти то, что на английском языке принято называть словом interleaving. Возможные варианты чередования:

а b c d e f

a b d c e f

a b d e c f

a b d e f c

a d b c e f

......

d e f a b c

Атомарные операции активностей могут чередоваться всевозможными различными спо­собами с сохранением порядка расположения внутри активностей. Так как псевдопа­раллельное выполнение двух активностей приводит к чередованию их неделимых опе­раций, результат псевдопараллельного выполнения может отличаться от результата последовательного выполнения. Рассмотрим пример. Пусть у нас имеется две активно­сти P и Q, состоящие из двух атомарных операций каждая:

P: x=2 Q: x=3

y=x-1 y=x+1

Что мы получим в результате их псевдопараллельного выполнения, если переменные x и y являются для активностей общими? Очевидно, что возможны четыре разных набора значений для пары (x, y): (3, 4), (2, 1), (2, 3) и (3, 2). Мы будем говорить, что набор активностей (например, программ) детерминирован, если всякий раз при псев­допараллельном исполнении для одного и того же набора входных данных он дает оди­наковые выходные данные. В противном случае он недетерминирован. Выше приведен пример недетерминированного набора программ. Понятно, что детерминированный на­бор активностей можно безбоязненно выполнять в режиме разделения времени. Для недетерминированного набора такое исполнение нежелательно.

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

Введем наборы входных и выходных переменных программы. Для каждой атомарной операции наборы входных и выходных переменных – это наборы переменных, которые атомарная операция считывает и записывает. Набор входных переменных программы R(P) (R от слова read) суть объединение наборов входных переменных для всех ее не­делимых действий. Аналогично, набор выходных переменных программы W(P) (W от слова write) суть объединение наборов выходных переменных для всех ее неделимых действий. Например, для программы

P: x=u+v

y=x*w

получаем R(P) = {u, v, x, w}, W(P) = {x, y}. Заметим, что переменная x присутст­вует как в R(P), так и в W(P).

Теперь сформулируем условия Бернстайна.

Если для двух данных активностей P и Q:

  • пересечение W(P) и W(Q) пусто,

  • пересечение W(P) с R(Q) пусто,

  • пересечение R(P) и W(Q) пусто,

тогда выполнение P и Q детерминировано.

Если эти условия не соблюдены, возможно, параллельное выполнение P и Q детермини­ровано, а может быть, и нет.

Случай двух активностей естественным образом обобщается на их большее количество.

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

Про недетерминированный набор программ (и активностей вообще) говорят, что он имеет race condition (состояние гонки , состояние состязания). В приведенном выше примере процессы состязаются за вычисление значений переменных x и y.

Задачу упорядоченного доступа к разделяемым данным (устранение race condition) в том случае, когда нам не важна его очередность, можно решить, если обеспечить каж­дому процессу эксклюзивное право доступа к этим данным. Каждый процесс, обра­щающийся к разделяемым ресурсам, исключает для всех других процессов возмож­ность одновременного общения с этими ресурсами, если это может привести к недетер­минированному поведению набора процессов. Такой прием называется взаимоисклю­чением (mutual exclusion). Если очередность доступа к разделяемым ресурсам важна для получения правильных результатов, то одними взаимоисключениями уже не обой­тись, нужна взаимосинхронизация поведения программ.
^

Критическая секция


Важным понятием при изучении способов синхронизации процессов является понятие критической секции (critical section) программы. Критическая секция – это часть про­граммы, исполнение которой может привести к возникновению race condition для опре­деленного набора программ. Чтобы исключить эффект гонок по отношению к некото­рому ресурсу, необходимо организовать работу так, чтобы в каждый момент времени только один процесс мог находиться в своей критической секции, связанной с этим ре­сурсом. Иными словами, необходимо обеспечить реализацию взаимоисключения для критических секций программ. Реализация взаимоисключения для критических секций программ с практической точки зрения означает, что по отношению к другим процес­сам, участвующим во взаимодействии, критическая секция начинает выполняться как атомарная операция. Давайте рассмотрим следующий пример, в котором псевдопарал­лельные взаимодействующие процессы представлены действиями различных студен­тов:

Здесь критический участок для каждого процесса – от операции «Обнаруживает, что хлеба нет» до операции «Возвращается в комнату» включительно. В результате отсут­ствия взаимоисключения мы из ситуации «Нет хлеба» попадаем в ситуацию «Слишком много хлеба». Если бы этот критический участок выполнялся как атомарная операция – «Достает два батона хлеба», то проблема образования излишков была бы снята.

Таблица 5.1.

Время

Студент 1

Студент 2

Студент 3

17-05

Приходит в комнату







17-07

Обнаруживает,что хлеба нет







17-09

Уходит в магазин







17-11




Приходит в комнату




17-13




Обнаруживает, что хлеба нет




17-15




Уходит в магазин




17-17







Приходит в комнату

17-19







Обнаруживает,что хлеба нет

17-21







Уходит в магазин

17-23

Приходит в магазин







17-25

Покупает 2 батона на всех







17-27

Уходит из магазина







17-29




Приходит в магазин




17-31




Покупает 2 батона на всех




17-33




Уходит из магазина




17-35







Приходит в магазин

17-37







Покупает 2 батона на всех

17-39







Уходит из магазина

17-41

Возвращается в комнату







17-43










17-45










17-47




Возвращается в комнату




17-49










17-51










17-53







Возвращается в комнату

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

Таблица 5.2.

Время

Студент 1

Студент 2

Студент 3

17-05

Приходит в комнату







17-07

Достает два батона хлеба







17-43




Приходит в комнату




17-47







Приходит в комнату

Итак, для решения задачи необходимо, чтобы в том случае, когда процесс находится в своем критическом участке, другие процессы не могли войти в свои критические уча­стки. Мы видим, что критический участок должен сопровождаться прологом (entry section) – «закрыть дверь изнутри на засов» – и эпилогом (exit section) – «отодви­нуть засов», которые не имеют отношения к активности одиночного процесса. Во время выполнения пролога процесс должен, в частности, получить разрешение на вход в кри­тический участок, а во время выполнения эпилога – сообщить другим процессам, что он покинул критическую секцию.

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

while (some condition) {

entry section

critical section

exit section

remainder section

}

Здесь под remainder section понимаются все атомарные операции, не входящие в критическую секцию.

Оставшаяся часть этой лекции посвящена различным способам программной организа­ции пролога и эпилога критического участка в случае, когда очередность доступа к критическому участку не имеет значения.
^

Программные алгоритмы организации взаимодействия процессов

Требования, предъявляемые к алгоритмам


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

  1. Задача должна быть решена чисто программным способом на обычной машине, не имеющей специальных команд взаимоисключения. При этом предполагается, что основные инструкции языка программирования (такие примитивные инст­рукции, как load, store, test) являются атомарными операциями.

  2. Не должно существовать никаких предположений об относительных скоростях вы­полняющихся процессов или числе процессоров, на которых они исполня­ются.

  3. Если процесс Pi исполняется в своем критическом участке, то не существует ника­ких других процессов, которые исполняются в соответствующих критиче­ских секциях. Это условие получило название условия взаимоисключения (mutual exclusion).

  4. Процессы, которые находятся вне своих критических участков и не собираются входить в них, не могут препятствовать другим процессам входить в их собст­венные критические участки. Если нет процессов в критических секциях и име­ются процессы, желающие войти в них, то только те процессы, которые не ис­полняются в remainder section, должны принимать решение о том, какой процесс войдет в свою критическую секцию. Такое решение не должно приниматься бес­конечно долго. Это условие получило название условия прогресса (progress).

  5. Не должно возникать неограниченно долгого ожидания для входа одного из про­цессов в свой критический участок. От того момента, когда процесс запросил разрешение на вход в критическую секцию, и до того момента, когда он это раз­решение получил, другие процессы могут пройти через свои критические уча­стки лишь ограниченное число раз. Это условие получило название условия ог­раниченного ожидания (bound waiting).

Надо заметить, что описание соответствующего алгоритма в нашем случае означает описание способа организации пролога и эпилога для критической секции.
^

Запрет прерываний


Наиболее простым решением поставленной задачи является следующая организация пролога и эпилога:

while (some condition) {

запретить все прерывания

critical section

разрешить все прерывания

remainder section

}

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

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

Переменная-замок


В качестве следующей попытки решения задачи для пользовательских процессов рас­смотрим другое предложение. Возьмем некоторую переменную, доступную всем про­цессам, с начальным значением равным 0. Процесс может войти в критическую секцию только тогда, когда значение этой переменной-замка равно 0, одновременно изменяя ее значение на 1 – закрывая замок. При выходе из критической секции процесс сбра­сывает ее значение в 0 – замок открывается (как в случае с покупкой хлеба студентами в разделе «Критическая секция»).

shared int lock = 0;

/* shared означает, что */

/* переменная является разделяемой */
while (some condition) {

while(lock); lock = 1;

critical section

lock = 0;

remainder section

}

К сожалению, при внимательном рассмотрении мы видим, что такое решение не удов­летворяет условию взаимоисключения, так как действие while(lock); lock = 1; не является атомарным. Допустим, процесс P0 протестировал значение переменной lock и принял решение двигаться дальше. В этот момент, еще до присвоения переменной lock значения 1, планировщик передал управление процессу P1. Он тоже изучает содержи­мое переменной lock и тоже принимает решение войти в критический участок. Мы по­лучаем два процесса, одновременно выполняющих свои критические секции.
^

Строгое чередование


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

shared int turn = 0;
while (some condition) {

while(turn != i);

critical section

turn = 1-i;

remainder section

}

Очевидно, что взаимоисключение гарантируется, процессы входят в критическую сек­цию строго по очереди: P0, P1, P0, P1, P0, ... Но наш алгоритм не удовлетворяет условию прогресса. Например, если значение turn равно 1, и процесс P0 готов войти в критиче­ский участок, он не может сделать этого, даже если процесс P1 находится в remainder section.
^

Флаги готовности


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

shared int ready[2] = {0, 0};

Когда i-й процесс готов войти в критическую секцию, он присваивает элементу массива ready[i] значение равное 1. После выхода из критической секции он, естественно, сбрасывает это значение в 0. Процесс не входит в критическую секцию, если другой процесс уже готов к входу в критическую секцию или находится в ней.

while (some condition) {

ready[i] = 1;

while(ready[1-i]);

critical section

ready[i] = 0;

remainder section

}

Полученный алгоритм обеспечивает взаимоисключение, позволяет процессу, готовому к входу в критический участок, войти в него сразу после завершения эпилога в другом процессе, но все равно нарушает условие прогресса. Пусть процессы практически од­новременно подошли к выполнению пролога. После выполнения присваивания ready[0]=1 планировщик передал процессор от процесса 0 процессу 1, который также выполнил присваивание ready[1]=1. После этого оба процесса бесконечно долго ждут друг друга на входе в критическую секцию. Возникает ситуация, которую принято на­зывать тупиковой (deadlock). (Подробнее о тупиковых ситуациях рассказывается в лек­ции 7.)
^

Алгоритм Петерсона


Первое решение проблемы, удовлетворяющее всем требованиям и использующее идеи ранее рассмотренных алгоритмов, было предложено датским математиком Деккером (Dekker). В 1981 году Петерсон (Peterson) предложил более изящное решение. Пусть оба процесса имеют доступ к массиву флагов готовности и к переменной очередности.

shared int ready[2] = {0, 0};

shared int turn;

while (some condition) {

ready[i] = 1;

turn =1-i;

while(ready[1-i] && turn == 1-i);

critical section

ready[i] = 0;

remainder section

}

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

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

Удовлетворение требований 1 и 2 очевидно.

Докажем выполнение условия взаимоисключения методом от противного. Пусть оба процесса одновременно оказались внутри своих критических секций. Заметим, что про­цесс Pi может войти в критическую секцию, только если ready[1-i] == 0 или turn == i. Заметим также, что если оба процесса выполняют свои критические секции одновре­менно, то значения флагов готовности для обоих процессов совпадают и равны 1. Могли ли оба процесса войти в критические секции из состояния, когда они оба одно­временно находились в процессе выполнения цикла while? Нет, так как в этом случае переменная turn должна была бы одновременно иметь начения 0 и 1 (когда оба про­цесса выполняют цикл, значения переменных измениться не могут). Пусть процесс P0 первым вошел в критический участок, тогда процесс P1 должен был выполнить перед вхождением в цикл while по крайней мере один предваряющий оператор (turn = 0;). Однако после этого он не может выйти из цикла до окончания критического участка процесса P0, так как при входе в цикл ready[0] == 1 и turn == 0, и эти значения не могут измениться до тех пор, пока процесс P0 не покинет свой критический участок. Мы пришли к противоречию. Следовательно, имеет место взаимоисключение.

Докажем выполнение условия прогресса. Возьмем, без ограничения общности, процесс P0. Заметим, что он не может войти в свою критическую секцию только при совместном выполнении условий ready[1] == 1 и turn == 1. Если процесс P1 не готов к выполне­нию критического участка, то ready[1] == 0, и процесс P0 может осуществить вход. Если процесс P1 готов к выполнению критического участка, то ready[1] == 1 и пере­менная turn имеет значение 0 либо 1, позволяя процессу P0 либо процессу P1 начать выполнение критической секции. Если процесс P1 завершил выполнение критического участка, то он сбросит свой флаг готовности ready[1] == 0, разрешая процессу P0 при­ступить к выполнению критической работы. Таким образом, условие прогресса выпол­няется.

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

Алгоритм булочной (Bakery algorithm)


Алгоритм Петерсона дает нам решение задачи корректной организации взаимодействия двух процессов. Давайте рассмотрим теперь соответствующий алгоритм для n взаимо­действующих процессов, который получил название алгоритм булочной, хотя примени­тельно к нашим условиям его следовало бы скорее назвать алгоритм регистратуры в поликлинике. Основная его идея выглядит так. Каждый вновь прибывающий клиент (он же процесс) получает талончик на обслуживание с номером. Клиент с наименьшим но­мером на талончике обслуживается следующим. К сожалению, из-за неатомарности операции вычисления следующего номера алгоритм булочной не гарантирует, что у всех процессов будут талончики с разными номерами. В случае равенства номеров на талончиках у двух или более клиентов первым обслуживается клиент с меньшим значе­нием имени (имена можно сравнивать в лексикографическом порядке). Разделяемые структуры данных для алгоритма – это два массива

shared enum {false, true} choosing[n];

shared int number[n];

Изначально элементы этих массивов инициируются значениями false и 0 соответст­венно. Введем следующие обозначения

(a,b) < (c,d), если a < c

или если a == c и b < d

max(a0, a1, ...., an) – это число k такое, что

k >= ai для всех i = 0, ...,n

Структура процесса Pi для алгоритма булочной приведена ниже

while (some condition) {

choosing[i] = true;

number[i] = max(number[0], ...,

number[n-1]) + 1;

choosing[i] = false;

for(j = 0; j < n; j++){

while(choosing[j]);

while(number[j] != 0 && (number[j],j) <

(number[i],i));

}

critical section

number[i] = 0;

remainder section

}

Доказательство того, что этот алгоритм удовлетворяет условиям 1 – 5, выполните само­стоятельно в качестве упражнения.

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

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

Команда Test-and-Set (проверить и присвоить 1)


О выполнении команды Test-and-Set, осуществляющей проверку значения логической переменной с одновременной установкой ее значения в 1, можно думать как о выпол­нении функции

int Test_and_Set (int *target){

int tmp = *target;

*target = 1;

return tmp;

}

С использованием этой атомарной команды мы можем модифицировать наш алгоритм для переменной-замка, так чтобы он обеспечивал взаимоисключения

shared int lock = 0;
while (some condition) {

while(Test_and_Set(&lock));

critical section

lock = 0;

remainder section

}

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

Команда Swap (обменять значения)


Выполнение команды Swap, обменивающей два значения, находящихся в памяти, можно проиллюстрировать следующей функцией:

void Swap (int *a, int *b){

int tmp = *a;

*a = *b;

*b = tmp;

}

Применяя атомарную команду Swap, мы можем реализовать предыдущий алгоритм, введя дополнительную логическую переменную key, локальную для каждого процесса:

shared int lock = 0;

int key;
while (some condition) {

key = 1;

do Swap(&lock,&key);

while (key);

critical section

lock = 0;

remainder section

}

Заключение


Последовательное выполнение некоторых действий, направленных на достижение оп­ределенной цели, называется активностью. Активности состоят из атомарных операций, выполняемых неразрывно, как единичное целое. При исполнении нескольких активно­стей в псевдопараллельном режиме атомарные операции различных активностей могут перемешиваться между собой с соблюдением порядка следования внутри активностей. Это явление получило название interleaving (чередование). Если результаты выполне­ния нескольких активностей не зависят от варианта чередования, то такой набор ак­тивностей называется детерминированным. В противном случае он носит название не­детерминированного. Существует достаточное условие Бернстайна для определения детерминированности набора активностей, но оно накладывает очень жесткие ограни­чения на набор, требуя практически не взаимодействующих активностей. Про недетер­минированный набор активностей говорят, что он имеет race condition (условие гонки, состязания). Устранение race condition возможно при ограничении допустимых вариан­тов чередований атомарных операций с помощью синхронизации поведения активно­стей. Участки активностей, выполнение которых может привести к race condition, назы­вают критическими участками . Необходимым условием для устранения race condition является организация взаимоисключения на критических участках: внутри соответст­вующих критических участков не может одновременно находиться более одной актив­ности.

Для эффективных программных алгоритмов устранения race condition помимо условия взаимоисключения требуется выполнение следующих условий: алгоритмы не исполь­зуют специальных команд процессора для организации взаимоисключений, алгоритмы ничего не знают о скоростях выполнения процессов, алгоритмы удовлетворяют усло­виям прогресса и ограниченного ожидания. Все эти условия выполняются в алгоритме Петерсона для двух процессов и алгоритме булочной – для нескольких процессов.

Применение специальных команд процессора, выполняющих ряд действий как атомар­ную операцию, – Test-and-Set, Swap – позволяет существенно упростить алгоритмы синхронизации процессов.

КЛЮЧЕВЫЕ СЛОВА

interleaving (чередование), активность, атомарная операция, взаимоисключение (mutual exclusion), взаимосинхронизация, детерминированный набор процессов, недетермини­рованный набор процессов, состояние гонки (race condition), условия Бернстайна

Добавить документ в свой блог или на сайт

Похожие:

Алгоритмы синхронизации iconТема урока : «Базовые алгоритмические структуры. Циклические алгоритмы»
На предыдущих уроках было введено понятие алгоритма, рассмотрены такие базовые алгоритмические структуры, как линейный и условный...

Алгоритмы синхронизации iconНе удостоенные света Булгаков и Мандельштам: попытка синхронизации
С раннего детства помнится интригующее, будоражащее, завлекающее – чуковское: «У меня зазвонил телефон»

Алгоритмы синхронизации iconЗадание Определить основные алгоритмы распознавания образов, выявить...
Способность распознавать считается одним из основных свойств человека. Образ представляет собой описание объекта. Так из множества...

Алгоритмы синхронизации icon«Алгоритмы и исполнители»

Алгоритмы синхронизации iconАлгоритмы
Задачная формулировка: Прочитайте отрывки из русской народной сказки «Колобок», знакомой вам с детства

Алгоритмы синхронизации iconСамостоятельная работа 134
Уметь разрабатывать вычислительные алгоритмы решения широкого круга задач в общеинженерных и специальных

Алгоритмы синхронизации iconСамостоятельная работа 133
Уметь разрабатывать вычислительные алгоритмы решения широкого круга задач в общеинженерных и специальных

Алгоритмы синхронизации iconАлгоритмы. Что это такое?
Интересно оформлена презентация, подобраны стихи, замечательные картинки, подробно описан план работы на всех этапах

Алгоритмы синхронизации iconРабочая учебная программа по дисциплине численные методы в инженерных расчетах
Уметь разрабатывать вычислительные алгоритмы решения широкого круга задач в общеинженерных и специальных

Алгоритмы синхронизации iconПрограмма дисциплины утверждаю
...



Школьные материалы


При копировании материала укажите ссылку © 2018
контакты
top-bal.ru

Поиск