Бум на Linux, програмиране, статии, библиотека на Linux Center, експерт по Linux и безплатен софтуер

Наскоро много водещи доставчици на софтуер обявиха пренасяне на своите продукти към Linux, безплатна операционна система. В интернет постоянно се появяват нови сайтове, посветени на тази система. Има бум около Linux, който не само не спира, но става все по-силен и по-мощен.

Linux, като клонинг на Unix, в момента поддържа многозадачност и многонишковост, т.е. Няколко задачи (процеси) могат да работят в системата едновременно, като всяка от задачите може да се изпълнява в няколко нишки.

Първо, нека да разгледаме какво е нишка: нишката на изпълнение е елемент от програмния код, който се изпълнява последователно. Повечето приложения са еднонишкови програми. Многонишкова програма може да се изпълнява в няколко отделни нишки едновременно.

Ако задачата се изпълнява на многопроцесорна машина, тогава всички нейни нишки могат да се изпълняват едновременно, като по този начин се повишава производителността на задачата. Производителността на многопоточно приложение може да бъде подобрена дори на еднопроцесорна система. Например, ако една от нишките на приложението е блокирана от някакво системно извикване или чака да пристигнат данни, друга нишка работи по това време.

Има разлики между процеси и нишки. Процесът е програма, която се изпълнява. Да кажем, че UNIX shell е процес, който се създава, когато потребител влезе в системата. Всяка команда създава нов процес. В терминологията на UNIX, дъщерен процес, който изпълнява команда от името на потребителя. Нишките са част от процес и споделят данни и кодови сегменти.

Има два основни стандарта за многонишково програмиране: многонишкови API на Solaris (Sun Microsystems) иPOSIX.1c API Linux използва POSIX.1c API. Но за да бъдем абсолютно точни, Linux има системното извикване clone(), на базата на което се изгражда API за работа с нишки, отговарящи на стандарта POSIX.1c с малки изключения.Дефиниране на проблем

Доста често при инженерни изчисления е необходимо да се изчисли матрица, чиито елементи са функции, или по-скоро стойността на функция с определени параметри. Разгледайте следната 4x4 матрица A: X44) където f(x) е изчислената функция Xij е аргументът >Стандартен подход към изчисленията: предимства и недостатъци

За изчисляване на елементите на тази матрица обикновено се използва следният кодов фрагмент: Ако приемем, че изходните данни (Xij) се съхраняват в X масива, а изходните данни в S:

int SIZE_I = 4; int SIZE_J = 4; двойно X[SIZE_I][SIZE_J]; двойно S[SIZE_I][SIZE_J]; . двойно f(двойно x) . //някои изчисления > main_elution() for (int i=0;i for (int z=0; z //изчисляване на матричен елемент X[i][z] = f(S[i][z]); > >

След изпълнение на този код, матрицата X ще бъде попълнена с изчислените данни. Предимството на този подход е лекотата на изпълнение. Недостатък - при работа на мощна машина (особено с много процесори) непълно използване на изчислителните ресурси.

Многопоточно изчисление: предимства и недостатъци

За да преобразувате предишния пример в многонишкова задача, трябва да направите малка промяна - функцията, която изчислява стойността, трябва да работи в отделна нишка.

int SIZE_I = 4; int SIZE_J = 4; двойноX[SIZE_I][SIZE_J]; двойно S[SIZE_I][SIZE_J]; struct DATA_ двойно x; int i; intz; > typedef структура DATA_ DATA; double f(double x) //някои изчисления > void *thread_f(void *arg) //функция за изчисляване на матричния елемент DATA* a = (DATA*)arg; //трансформиране на данни X[a->i][a->z] = f([a->x]); //изчисляване > main_elution() pthread_t нишка; //ИД на поток ДАННИ *arg; //данни за предаване към потока for (int i=0;i for (int z=0; z // създаване на arg = new DATA; //инициализиране на данни arg->i = i; arg->z = z; arg->x = S[i][z]; //създаване на нишка pthread_create(& ;thread) , NULL, thread_f, (void *)arg); //състояние на отделяне pthread_detach(нишка); > > >

В резултат на направените промени изчисляването на всеки елемент ще се извърши в отделна нишка. Недостатъкът на този метод е сложността - винаги трябва да имате предвид факта, че две нишки могат да имат достъп до едни и същи данни - едната за четене, другата за запис и в този случай не може да се гарантира валидността на данните. Тези. необходимо е да се зададат/проверят ключалки, да се осигури синхронизация на изпълнението и др. Ползата е повишена производителност. Така че в нашия пример процесът не чака всички нишки да завършат, т.е. не чака да се запълнят всички матрични елементи, а продължава работата си. В случай, че по-нататъшната работа на програмата зависи от получените изчисления, е възможно да спрете основния процес, докато всички нишки бъдат завършени.Функции за работа с нишки

Основните функции за работа с нишки са: pthread_create(pthread_t *tid, const pthread_attr_t *attr, void*(*function)(void*), void* arg) - създава нишка заизпълнение на функцията. Указателят arg се предава като параметър към функцията поток. Идентификаторът на новата нишка се връща чрез tid. Потокът се създава с параметри attr. · pthread_mutex_init(pthread_mutex_t* заключване, pthread_mutexattr_t *attr) - инициализира заключване на mutex. attr - съдържа атрибути за взаимно изключваща се ключалка. В случай че attr == NULL се използват настройките по подразбиране. · pthread_mutex_destroy(pthread_mutex_t* lock) - премахва заключване на mutex. · pthread_mutex_lock(pthread_mutex_t* lock) - задава заключване. Ако заключването е получено от друг процес, текущият процес спира, докато заключването не бъде освободено от друг процес. · pthread_mutex_unlock(pthread_mutex_t* lock) - освобождава заключването. · pthread_join(pthread_t tid, void **statusp) - изчаква неоткачения процес да приключи, резултатът, върнат от функцията, се съхранява в statusp. · pthread_detach(pthread_t tid) - Отделяне на процес. Това също може да бъде зададено при създаване на процес чрез задаване на атрибута detachstate чрез извикване на pthread_attr_setdetachstate. · pthread_exit(void *status) - прекратява процеса, състоянието се предава на извикването pthread_join, подобно на exit(). Но извикването на exit() в процес ще прекрати цялата програма. Процесът се прекратява по два начина - чрез извикване на pthread_exit() или чрез прекратяване на функцията на нишката. Ако процесът не е отделен, тогава, когато той приключи, ресурсите, разпределени за процеса, не се освобождават, докато не бъде извикан pthread_join(). Ако процесът е отделен, ресурсите се освобождават, когато той приключи.Пример на програма

Тази програма пита потребителя за параметрите на матрицата от аргументи и използвайки потоци, попълва матрицата с резултатите от изчисленията. Тази програма се нуждаекомпилиране с библиотеката pthread (тя съдържа всички функции за работа с нишки) и указване на _REENTRANT: g++ -D_REENTRANT -o нишки threads.c -lpthread Този код е тестван на RedHat Linux 6.0

* threads.c * проста демонстрация на API за pthread * автор: Тарасенко Володимир * имейл: [email protected] * Компилиране: * g++ -D_REENTRANT -o нишки threads.c -lpthread */ #include #include #include #include #define SIZE_I 2 #define SIZE_J 2 float X[SIZE_I][SIZE_J]; float S[SIZE_I][SIZE_J]; всички = 0; struct DATA_ двойно x; int i; intz; >; typedef структура DATA_ DATA;

pthread_mutex_t заключване; //Изключително заключване

// Изчислителна функция double f(float x) if (x>0) return log(x); иначе връща x; > // Нишка функция за изчисления void *thread_f(void *arg) DATA* a = (DATA*) arg;

X[a->i][a->z] = f(a->x); // задаване на заключването pthread_mutex_lock(&lock); // промяна на глобалната променлива ++all; // освобождаване на ключалката pthread_mutex_unlock(&lock);

изтрий a; // изтриваме нашите данни return NULL; > // Поточна функция за вход void *input_thr(void *arg) DATA* a = (DATA*) arg; //pthread_mutex_lock(&заключване); printf("S[%d][%d]:", a->i, a->z); scanf("%f", &S[a->i][a->z]); //pthread_mutex_unlock(&заключване); изтриване на a; връща NULL; > int main() //масив от идентификатори на нишки pthread_t thr[ SIZE_I + SIZE_J ]; //инициализиране на изключително заключване pthread_mutex_init(&lock, NULL); ДАННИ *arg; // Въведете for (int i=0;i for (int z=0; z arg = нови ДАННИ; arg->i = i; arg->z = z; //създайте входен поток pthread_create(&thr[i+z], NULL, input_thr, (void *)arg); > > //Изчакване за завършване на всички нишки //Идентификаторите на нишки се съхраняват в масив for(int i = 0; i pthread_join(thr[i], NULL); > //Изчисления printf("Начало на изчисление\n"); for (int i=0;i for (int z=0; z arg = new D ATA; ar g->i = i; arg->z = z; arg->x = S[i][z]; pthread_t нишка; //създаване на нишка за изчисление pthread_create(&thread, NULL, thread_f, (void *)arg); // превключване към отделен режим pthread_detach(нишка ); > > do // Основният процес "спи" за 1s sleep(1); // Всичко приключи ли? printf("завършени %d нишки.\n", всички); >while(all //Отпечатайте резултатите for (int i=0;i for (int z=0; z print f("X[%d ][%d] = %f\t", i, z, X[i][z]); > printf("\n"); > //Премахване на изключителното заключване pthread_mutex_destroy(&lock); return 0; >

При стартиране програмата инициализира изключителното заключване и започва да въвежда данни. В този случай, като пример, входът се прави от нишки, без никакво I/O блокиране, за да се покаже, че нишките се изпълняват едновременно и когато едната спре, другите продължават да работят. Основният процес изчаква всички нишки да прекратят чрез извикване на pthread_join(). Едва след завършване на всички нишки се преминава към втората част на програмата - изчисления. Откъснатите нишки се използват за изчисления, отделянето се извършва чрез извикване на pthread_detach(). След завършване на изчисленията в нишката, променливата all се увеличава с единица и нишката излиза. Използва се изключителна ключалка, за да се гарантира, че промените са направени правилно. След като забавите основния процес за 1 секунда, проверетеброя на завършените нишки и ако всички нишки имат завършени изчисления, показваме резултата от работата. Показаният пример ще бъде полезен при решаването на много проблеми. Особено при изчисления в областта на металообработването, при чието решаване често се използват методи на крайни елементи или методи на гранични елементи. Тези методи се характеризират с големи изчисления, свързани с матрици и тяхното попълване. В повечето случаи матричният елемент е резултат от сложни изчисления, като например решаване на интегрални уравнения. Използването на многонишков подход ще увеличи скоростта и производителността на изчисленията. Но, както е показано, това води до усложняване на изпълнението на изчисленията.