Програмування в ace: рівень інтерфейсних фасадів, багатозадачність і багатопоточність
Рівень інтерфейсних фасадівОписані вище механізми паралелізму реалізовані в ACE у вигляді інтерфейсних фасадів. Таким чином, ми піднімаємося з рівня адаптації до операційної системи на рівень фасадів ACE, інкапсулюючих API операційної системи. Виникає питання: а для чого потрібно використовувати фасади ACE, чи не простіше нам використовувати API операційної системи і сокети для реалізації конкретного завдання? Відповідь лежить на поверхні - по-перше, ми створюємо легко переносимо код, який можна буде використовувати повторно, а, по-друге, замість небезпечних з точки зору типо-безпеки дескрипторів функцій API, ми використовуємо инкапсулирующие їх фасади ACE.
багатозадачність
Як відомо, створення нового завдання здійснюється в POSIX-сумісних операційних системах за допомогою функції fork () (в Windows це можна зробити за допомогою функції CreateProcess ()). Замість цього ACE пропонує клас ACE_Process, за допомогою якого можна створювати процеси і керувати ними. За допомогою класу ACE_Process_Option програміст може задавати опції процесу, такі як командний рядок, змінні оточення, робочий каталог і їм подібні. Створимо утиліту, яка запускає програму, ім`я якої зазначено в якості аргументу командного рядка.
#include
#include
int main (int argc, char * argv [])
{
if (argcspawn ( process1, options);
// Чекаємо завершення всіх процесів, асоційованих з групою
return pPM-gt; wait (ACE_Time_Value :: max_time);
}
Нить
На відміну від багатозадачності, багатопоточність дозволяє різним потокам виконання працювати в єдиному адресному просторі процесу. І в більшості випадків многопоточность є більш зручною схемою для створення додатків, які обробляють множинні клієнтські запити. При програмуванні кроссплатформенних багатопоточних додатків програмісти стикаються не тільки з відмінностями в семантиці потокових API, але і з різними ідеологіями многопоточности. Наприклад, в Linux потоки є процесами особливого типу - легкими (lightweight) процесами. Вони створюються як дочірні процеси головного процесу. У Windows ми маємо головний процес, який є певним контейнером для потоків програми. В ACE потоки створюються і управляються за допомогою класу ACE_Thread_Manager. Аналогічно до вищеописаного менеджеру процесів, цим класом можна користуватися як Сінглтоном або створювати кілька примірників менеджера потоків для визначення різних груп потоків в рамках одного процесу. Напишемо невелику програму, яка створює три нескінченно виконуються потоку, і закриває їх після деякої паузи.
#include
#include
unsigned long thread (void * p)
{
// Імітуємо роботу потоку
ACE_OS :: printf ( "Hi there. I am #% i thread n", p);
// По ідеї ми повинні «заснути» на нескінченний час
// Але виклик cancel_all () в кінці main () закриває всі потоки
ACE_OS :: sleep (ACE_Time_Value :: max_time);
return 0;
}
int main (int argc, char * argv [])
{
// Створюємо три потоку
ACE_Thread_Manager * pTM = ACE_Thread_Manager :: instance ();
pTM-gt; spawn (thread, (void *) 0, THR_SCOPE_SYSTEM);
pTM -gt; spawn (thread, (void *) 1, THR_SCOPE_SYSTEM);
pTM -gt; spawn (thread, (void *) 2, THR_SCOPE_SYSTEM);
// Імітуємо якусь роботу основного потоку
ACE_OS :: sleep (1);
// Завершуємо все потоки
pTM-gt; cancel_all ();
// Чекаємо завершення всіх потоків
return pTM-gt; wait ( ACE_Time_Value :: max_time);
}
Одним з параметрів методу spawn () менеджера потоків є пріоритет потоку, і за замовчуванням це значення дорівнює ACE_DEFAULT_THREAD_PRIORITY. За допомогою класу ACE_Sched_Params можна вибирати потрібний пріоритет виконання і встановлювати його за допомогою функції sched_params з простору імен ACE_OS. Базовий пріоритет виконання для всіх потоків програми можна встановити на самому початку її роботи в функції main (). Таким чином, всі новостворювані потоки будуть виконуватися з цим пріоритетом. Нижченаведений код демонструє, як можна встановити пріоритет виконання.
#include
#include
int main (int argc, char * argv [])
{
ACE_Sched_Params schedParams (ACE_SCHED_FIFO,
ACE_Sched_Params :: priority_min (ACE_SCHED_FIFO),
ACE_SCOPE_PROCESS);
ACE_OS :: sched_params (schedParams);
return 0;
}
Синхронізація
Не секрет, що при створенні багатопоточних програм розробники стикаються з проблемою одночасного звернення до одних і тих же даних з різних потоків. Наприклад, одночасно відбуваються спроби записати і прочитати значення однієї і тієї ж змінної. Для того щоб уникнути подібних ситуацій, застосовуються механізми синхронізації, які дозволяють заблокувати виконання потоку до тих пір, поки що захопив даний ресурс потік не звільнить його. Існує кілька стандартизованих (і стандартизованих де-факто як, наприклад, Win32) API, що надають механізми синхронізації потоків. ACE надає набір уніфікованих класів, що реалізують всю необхідну розробникам функціональність.
Всі класи синхронізації надають уніфікований інтерфейс ACE_LOCK. Напишемо програму, основний потік якої змінює значення глобальної змінної, а допоміжний виводить її значення в консоль, коли вона змінюється. Фактично ми змоделюємо один з найпоширеніших випадків, коли потрібно синхронізувати роботу потоків.
#include
#include
#include
// Глобальний лічильник
static int gCounter (0);
// М`ютекс
static ACE_Thread_Mutex gMutex;
unsigned long thread (void * p)
{
int oldValue (-1);
while (1)
{
// запитуваний блокування
if (gMutex.acquire_read ()! = - 1)
{
// Якщо значення лічильника було інкрементіровать, виводимо його
if (gCountergt; oldValue)
{
ACE_OS :: printf ( "Counter is% i n", gCounter);
oldValue = gCounter;
}
}
// Знімаємо блокування
gMutex.release ();
}
return 0;
}
int main (int argc, char * argv [])
{
ACE_Thread_Manager * pTM = ACE_Thread_Manager :: instance ();
// Створюємо три потоку
int id;
id = pTM-gt; spawn (thread, (void *) 0, THR_SCOPE_SYSTEM);
if (id == - 1)
{
ACE_OS :: printf ( "Unable to create thread n");
return -1;
}
int i (10);
while (i)
{
// запитуваний блокування
if (gMutex.acquire_write ()! = - 1)
{
++gCounter;
}
// Знімаємо блокування
gMutex.release ();
--i;
// Імітуємо роботу програми
ACE_OS :: sleep (1);
}
// Завершуємо все потоки
pTM -gt; cancel_all ();
// Чекаємо завершення всіх потоків
return pTM -gt; wait ( ACE_Time_Value :: max_time);
}