Создание многопоточного приложения в C++

Поток как основная выполняемая единица, для которой операционная система выделяет процессорное время. Интервал (или период) системного таймера. Особенности статического типа планирования. Синхронизация, атомарный доступ и семейство Interlocked-функций.

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 27.02.2015
Размер файла 188,7 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

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

Размещено на http://www.allbest.ru/

2

[Введите текст]

СОДЕРЖАНИЕ

ВВЕДЕНИЕ

1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ

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

1.2 Описание проблемы

2. ТЕОРЕТИЧЕСКИЙ РАЗДЕЛ

2.1 Процессы и потоки

2.2 Синхронизация

3. ПРОЕКТИРОВАНИЕ ПРИЛОЖЕНИЯ

4. ИСХОДНЫЙ КОД

ЛИТЕРАТУРА

ПРИЛОЖЕНИЕ

ВВЕДЕНИЕ

Программа написана на языке С++ для платформы Windows NT.

Microsoft Windows произносится [мамйкрософт вимндоус]) - семейство проприетарных операционных систем корпорации Microsoft, ориентированных на применение графического интерфейса при управлении. Изначально Windows была всего лишь графической надстройкой для MS-DOS.

По состоянию на март 2013 года под управлением операционных систем семейства Windows по данным ресурса NetMarketShare (Net Applications) работает около 90% персональных компьютеров[1]. Операционные системы Windows работают на платформах x86, x86-64, IA-64 и ARM.

C++ - компилируемый статически типизированный язык программирования общего назначения.

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

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

В сравнении с его предшественником - языком C, - наибольшее внимание уделено поддержке объектно-ориентированного и обобщённого программирования[3].

Рассматривается создание многопоточного приложения.

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

Она должна отвечать следующим требованиям:

Наглядность представления информации;

Простота ввода информации;

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

1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ

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

Разработать программу, которая создает случайный массив A из 20 целых чисел в диапазоне от 0 до 99, выводит на экран эти числа, создает два потока, которые выполняют с этим массивом задания A и B соответственно. Все потоки выводят результаты своей работы в вертикальные колонки, каждый поток в свою колонку.

Поток A: вычисление сумм соседних элементов массива.

Поток B: последовательное вычисление среднего арифметического соседних чисел.

1.2 Описание проблемы

Планирование потоков, по существу, включает в себя решение двух задач:

- определение момента времени для смены текущего активного потока;

- выбор для выполнения потока из очереди готовых потоков.

2. ТЕОРЕТИЧЕСКИЙ РАЗДЕЛ

2.1 Процессы и потоки

При запуске приложения операционная система Windows создает процесс. Процесс (process) - это совокупность ресурсов, необходимых для выполнения программы. Процесс владеет виртуальным адресным пространством, выполняемым кодом, данными, дескрипторами необходимых объектов и иными ресурсами. Однако сам по себе процесс не выполняется. Вместо этого он запускает единственный поток, который часто называют первичным потоком (primary thread). Если процесс имеет только один первичный поток, то, фактически, понятия «процесс» и «поток» совпадают. Первичный поток может создавать другие потоки, те, в свою очередь, новые потоки и т. д.

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

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

2.2 Планирование потоков

Чтобы все потоки работали, операционная система выделяет каждому из них определенное процессорное время. Тем самым создается иллюзия одновременного выполнения потоков. Разумеется, для многопроцессорных систем возможен истинный параллелизм.

Каждый поток может находиться в одном из трех состояний, показанных на рисунке 1.

Рисунок 1 - Диаграмма состояний потока

В однопроцессорной системе в любой момент времени только один поток может находиться в состоянии выполнения. Все остальные потоки находятся либо в состоянии готовности, либо в состоянии блокировки.

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

Выбранный для выполнения поток работает в течение некоторого периода, называемого квантом. Windows оперирует квантом потока не как отрезком времени, а как целым числом. Обычно поток стартует со значением кванта, равным 6 - для Windows 2000 Professional или 36 - для Windows 2000 Server.

Каждый раз, когда возникает прерывание от системного таймера, из кванта выполняющегося потока вычитается фиксированное значение 3, и так продолжается до тех пор, пока значение кванта не достигнет нуля. Поэтому под управлением Windows 2000 Professional поток будет выполняться в течение двух интервалов системного таймера, а под управлением Windows 2000 Server - в течение 12 интервалов.

Интервал (или период) системного таймера обычно равен 10 мс или около 15 мс, в зависимости от аппаратной платформы. Точное значение этого интервала можно получить с помощью функции GetSystemTimeAdjustment (см. главу 10). Например, для моего компьютера с процессором Intel Celeron CPU 2.0 ГГц период срабатывания системного таймера равен 15,625 мс. При таком периоде кванту потока соответствует временной интервал 15,625 * 2 - 31,25 мс.

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

Однако выполняемый поток не всегда полностью использует свой квант. Его выполнение может быть прервано при ненулевом кванте в двух ситуациях:

когда появился в состоянии готовности другой поток с более высоким приоритетом; при этом текущий поток вытесняется и переводится в состояние готовности;

текущему потоку потребовался какой-либо системный ресурс (или объект ядра), который в настоящий момент времени является занятым; в этом случае поток переводится в состояние блокировки (ожидания события).

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

Существует множество различных алгоритмов планирования потоков, по-своему решающих каждую из приведенных выше задач. Алгоритмы планирования могут преследовать различные цели и обеспечивать разное качество мультипрограммирования. Например, в одном случае выбирается такой алгоритм планирования, при котором гарантируется, что ни один поток/процесс не будет занимать процессор дольше определенного времени, в другом случае целью является максимально быстрое выполнение «коротких» задач, а в третьем случае - преимущественное право занять процессор получают потоки интерактивных приложений. Именно особенности реализации планирования потоков в наибольшей степени определяют специфику операционной системы, в частности, является ли она системой пакетной обработки, системой разделения времени или системой реального времени.

В большинстве операционных систем универсального назначения планирование осуществляется динамически (on-line), то есть решения принимаются во время работы системы на основе анализа текущей ситуации. ОС работает в условиях неопределенности - потоки и процессы появляются в случайные моменты времени и также непредсказуемо завершаются. Динамические планировщики могут гибко приспосабливаться к изменяющейся ситуации и не используют никаких предположений о мультипрограммной смеси. Для того чтобы оперативно найти в условиях такой неопределенности оптимальный в некотором смысле порядок выполнения задач, операционная система должна затрачивать значительные усилия.

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

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

Диспетчеризация сводится к следующему;

- сохранение контекста текущего потока, который требуется сменить;

- загрузка контекста нового потока, выбранного в результате планирования;

- запуск нового потока на выполнение.

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

В состоянии выполнения в однопроцессорной системе может находиться не более одного потока, а в каждом из состояний ожидания и готовности - несколько потоков. Эти потоки образуют очереди соответственно ожидающих и готовых потоков. Очереди потоков организуются путем объединения в списки описателей отдельных потоков. Таким образом, каждый описатель потока, кроме всего прочего, содержит по крайней мере один указатель на другой описатель, соседствующий с ним в очереди. Такая организация очередей позволяет легко их переупорядочивать, включать и исключать потоки, переводить потоки из одного состояния в другое. Если предположить, что на рисунке 2. показана очередь готовых потоков, то запланированный порядок выполнения выглядит так: А, В, Е, D, С.[4]

Рисунок 2 - Очередь потоков

2.3 Синхронизация

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

- совместно используя разделяемый ресурс (чтобы не разрушить его);

- когда нужно уведомить другие потоки о завершении каких-либо операций.

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

- атомарные операции API-уровня;

- критические секции;

- события;

- ожидаемые таймеры;

- семафоры;

- мыотексы.

Каждый из указанных типов примитивов синхронизации применяется в определенных ситуациях.

Синхронизация потоков в пользовательском режиме

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

Все потоки в системе должны иметь доступ к системным ресурсам - кучам, последовательным портам, файлам, окнам и т. д. Если один из потоков запросит монопольный доступ к какому-либо ресурсу, другим потокам, которым тоже нужен этот ресурс, не удастся выполнить свои задачи. А с другой стороны, просто недопустимо, чтобы потоки бесконтрольно пользовались ресурсами. Иначе может получиться так, что один поток пишет в блок памяти, из которого другой что-то считывает. Представьте, Вы читаете книгу, а в это время кто-то переписывает текст на открытой Вами странице. Ничего хорошего из этого не выйдет.[5]

Потоки должны взаимодействовать друг с другом в двух основных случаях:

- совместно используя разделяемый ресурс (чтобы не разрушить его);

- когда нужно уведомлять другие потоки о завершении каких-либо операций.

Атомарный доступ и семейство Interlocked-функций

Большая часть синхронизации потоком связана с атомарным доступом {atomic access) - монопольным захватом ресурса обращающимся к нему потоком. Win32 API предоставляет несколько функций для реализации взаимно блокированных операций. Все Interlocked-функций работают корректно только при условии, что их аргументы выровнены по границе двойного слова (DWORD).

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

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

Синхронизация потоков с использованием объектов ядра.

Wait-функции

Многие объекты ядра могут находиться либо в свободном (signaled state), либо в занятом состоянии (nonsignaled state). К таким объектам относятся; а процессы;

-потоки;

-задания;

- файлы;

- консольный ввод;

-уведомления об изменении файлов;

- события;

- ожидаемые таймеры;

- семафоры;

- мьютексы.

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

События

Событие (extent) - самая простая разновидность объектов ядра. Оно содержит счетчик количества пользователей и две булевы переменные. Одна переменная указывает тип данного объекта-события, а другая - его состояние.

События просто уведомляют об окончании какой-либо операции. Объекты-события бывают двух типов: со сбросом вручную (manual-reset events) или с авто-сбросом (auto-reset events). Первые события позволяют возобновить выполнение сразу нескольких ждущих потоков, а вторые - только одного потока.

Семафоры

Объекты ядра «семафор» (semaphore) используются для учета ресурсов. Кроме счетчика числа пользователей семафор содержит два 32-битных значения со знаком. Одно из них определяет максимальное количество ресурсов, контролируемых семафором, а другое используется как счетчик текущего числа ресурсов.

Мьютексы

Объекты ядра «мьютексы» (mutex) гарантируют потокам взаимоисключающий доступ к единственному ресурсу. Кроме счетчика пользователей мыотекс содержит счетчик рекурсии и переменную, в которой запоминается идентификатор потока-владельца. Мьютексы ведут себя подобно критическим секциям. Однако если критические секции являются объектами пользовательского режима, то мьютексы - это объекты ядра. Поэтому они позволяют синхронизировать доступ к ресурсу нескольких потоков из разных процессов.

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

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

Объект ядра «процесс» пребывает в занятом состоянии, пока выполняется сопоставленный с ним процесс, и переходит в свободное состояние, когда процесс завершается. Внутри этого объекта поддерживается булева переменная, которая при создании объекта инициализируется как FALSE («занято»). По окончании работы процесса операционная система меняет значение этой переменной на TRUE, сообщая тем самым, что объект свободен.[5]

3. ПРОЕКТИРОВАНИЕ ПРИЛОЖЕНИЯ

поток интервал синхронизация

Приложение будет работать в консольном режиме.

Работа задания A.

Вывод значений m[0] +m[1].

Вывод значений m[i-1] + m[i] +m[i+1].

Вывод значений m[N-1] + m[N-2] .

Где m - массив, N-размер, i - индекс.

Задание A будет выполняться в потоке ThreadFuncA.

Работа задания B.

Вывод значений (m[0] +m[1])/2.

Вывод значений (m[i-1] + m[i] +m[i+1])/3.

Вывод значений (m[N-1] + m[N-2])/2 .

Где m - массив, N-размер, i - индекс.

Задание B будет выполняться в потоке ThreadFuncB.

Результат работы приложения представлен на рисунке 3.

4. ИСХОДНЫЙ КОД

// Kurs_1.cpp: определяет точку входа для консольного приложения.

//

#include "stdafx.h"

#include <cstdlib>

#include <iostream>

#include <windows.h>

#include <time.h>

using namespace std;

const int _SIZE = 20; //размер массива

int g_nNums[_SIZE]; // разделяемый ресурс

HANDLE mut; // защищает ресурс

DWORD WINAPI ThreadFuncA(LPVOID lpv)

{

WaitForSingleObject(mut, INFINITE); //ждёт освобождения мьютекса

cout<<"\n\nThread A:\n";

cout<<g_nNums[0]+g_nNums[1]<<"\n"; //расчёт и вывод m[0]+m[1]

for (int x = 1; x < _SIZE-1; x++) {

cout<<g_nNums[x-1]+g_nNums[x]+g_nNums[x+1]; //расчёт и вывод основных значений

cout<<"\n";

}

cout<<g_nNums[_SIZE-1]+g_nNums[_SIZE-2]<<"\n";//расчёт и вывод m[N-1]+m[N-2]

return(0);

}

DWORD WINAPI ThreadFuncB(LPVOID lpv)

{

WaitForSingleObject(mut, INFINITE); //ждёт освобождения мьютекса

cout<<"\n\nThread B:\n";

cout<<((float)(g_nNums[0]+g_nNums[1])/2.0)<<"\n";//расчёт и вывод (m[0]+m[1])/2

for (int x = 1; x < _SIZE-1; x++) {

cout<<((float)(g_nNums[x-1]+g_nNums[x]+g_nNums[x+1])/3.0);//расчёт и вывод основных значений

cout<<"\n";

}

cout<<((float)(g_nNums[_SIZE-1]+g_nNums[_SIZE-2])/2.0)<<"\n";//расчёт и вывод m[N-1]+m[N-2]/2

return(0);

}

int main(int argc, char *argv[])

{

srand(time(NULL)); //рандомизирование входных значений

//присваивание рандомных значений элементам массива

for (int x = 0; x < _SIZE; x++) {

g_nNums[x]=rand()%100;

}

//инициализация мьютекса

mut=CreateMutex(NULL,FALSE, L"MyMutex");

cout<<"Matrix:\n";

//вывод значений элементов массива

for (int x = 0; x < _SIZE; x++) {

cout<<g_nNums[x]<<" ";

}

cout<<"\n\n";

//создание потоков A и B

HANDLE hThread1 = CreateThread (NULL, 0, ThreadFuncA, NULL,0,NULL);

HANDLE hThread2 = CreateThread (NULL,0,ThreadFuncB, NULL,0,NULL);

//ожидание завершения потоков

WaitForSingleObject(hThread1, INFINITE);

WaitForSingleObject(hThread2, INFINITE);

cout<<"\n";

system("PAUSE");

return EXIT_SUCCESS;

}

ЛИТЕРАТУРА

1. Ден Томашевский. Microsoft Windows 8. Руководство пользователя = Microsoft Windows 8. Руководство пользователя. - Вильямс, 2013. - С. 352.

2. Бьёрн Страуструп. Язык программирования C++ = The C++ Programming Language / Пер. с англ. - 3-е изд. - СПб.; М.: Невский диалект - Бином, 1999. - 991 с.

3. Бьёрн Страуструп Язык программирования C++. Специальное издание = The C++ programming language. Special edition. - М.: Бином-Пресс, 2007. - 1104 с. - ISBN 5-7989-0223-4.

4. Сетевые операционные системы / В. Г. Олифер, Н. А. Олифер. - СПб.: Питер, 2002. - 544 с: ил. ISBN 5-272-00120-6.

5. Рихтер Дж. Windows для профессионалов: создание эффективных Win32-npi«io3iceHHft с учетом специфики 64-разрядной версии Windows / Пер. с англ. - 4-е изд. - Спб.: Питер; М.: Издательство «-Русская Редакция»; 2008. - 720 с.

6. ISBN 5-272-00384-5 («Питер»).

7. ISBN 978-5-7502-0360-4 («Русская Редакция»).

ПРИЛОЖЕНИЕ

Рисунок 3 - Результат работы приложения

Размещено на Allbest.ru


Подобные документы

  • Функции системного блока, монитора, клавиатуры, мыши, принтера. Операционная система компьютера Microsoft Windows, офисные приложения. Работа с таблицами: элементы окна Excel, создание диаграммы, базы данных, их поиск и замена. Работа с мастером функций.

    контрольная работа [578,5 K], добавлен 27.11.2010

  • Обзор мобильной операционной системы ios: Архитектура ОС iOS; уровень библиотек; среды разработки приложения (Xcode, Xamarin). Доступ к информации колледжа "Угреша". Требования к мобильному приложению. Подготовка среды разработки. Тестирование приложения.

    дипломная работа [5,6 M], добавлен 10.07.2014

  • Представление о потоках выполнения. Последовательный алгоритм ходьбы, бега и быстрого шага. Создание потоков на основе класса Thread и интерфейса Runnable, в Java. Синхронизация на ресурсах, объектах, методах, событиях. Константы и методы класса Thread.

    лекция [556,1 K], добавлен 01.05.2014

  • Генерация звука и обработка прерываний. Создание системы с использованием средств языка программирования Ассемблер. Установка и чтение таймера. Программирование микросхемы таймера 8253/8254. Максимальный программируемый интервал времени для системы.

    реферат [21,4 K], добавлен 10.05.2011

  • Виды, назначение и типовые функции операционных систем (ОС). Современные версии ОС для персональных компьютеров типа РС. Операционная система DOS. Операционная оболочка Windows. Базовая система ввода-вывода. Создание документированного интерфейса.

    контрольная работа [23,1 K], добавлен 29.03.2011

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

    дипломная работа [2,2 M], добавлен 24.06.2012

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

    презентация [21,3 K], добавлен 05.04.2011

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

    реферат [391,5 K], добавлен 28.12.2007

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

    курсовая работа [4,3 M], добавлен 27.03.2011

  • Исследование назначения, основных функций и характеристик операционных систем. Операционная система OS/2: исторический обзор и принципиальные особенности последнего поколения. Управление памятью. Устройства, файловая система и средства взаимодействия.

    курсовая работа [1,2 M], добавлен 17.02.2015

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.