5-bo‘lim
Oqimlar (threads)
Oqim nima, jarayondan farqi, pthread bilan ishlash, umumiy xotira, poyga holati va oqimlarni qachon ishlatish kerak.
Ushbu bo‘lim mundarijasi
Jarayon yaratish qimmat va ular bir-birining xotirasini ko'rmaydi. Oqim (thread) esa bitta jarayon ichida ishlaydi va xotirani bo'lishadi.
Jarayon va oqim #
| Jarayon | Oqim | |
|---|---|---|
| Xotira | Alohida | Umumiy |
| Stek | O'zi | O'zi |
| Yaratish narxi | Yuqori (~100 mks) | Past (~10 mks) |
| Almashinuv narxi | Yuqori | Past |
| Ma'lumot almashish | Qiyin (IPC kerak) | Oson (o'zgaruvchi) |
| Xavfsizlik | Biri qulasa - boshqasi tirik | Biri qulasa - hammasi |
| Xato xavfi | Past | Yuqori (poyga holati) |
Birinchi oqim #
#include <stdio.h>
#include <pthread.h>
void *ish(void *argument) {
int raqam = *(int *) argument;
printf(" Oqim %d ishlayapti\n", raqam);
return NULL;
}
int main(void) {
pthread_t oqim;
int raqam = 1;
if (pthread_create(&oqim, NULL, ish, &raqam) != 0) {
printf("Oqim yaratilmadi\n");
return 1;
}
pthread_join(oqim, NULL); /* oqim tugashini kutamiz */
printf("Asosiy oqim tugadi\n");
return 0;
}
Oqim 1 ishlayapti
Asosiy oqim tugadi
-pthread kerakgcc -std=c17 -Wall -Wextra -pthread dastur.c -o dastur
-pthread ni unutsangiz:
undefined reference to `pthread_create'
Bu bayroq kutubxonani bog'laydi va kerakli makroslarni yoqadi.
-lpthread emas, aynan -pthread yozing.
Bir nechta oqim #
#include <stdio.h>
#include <pthread.h>
#define OQIMLAR 4
typedef struct {
int raqam;
long natija;
} Vazifa;
void *yigindi_hisobla(void *arg) {
Vazifa *v = arg;
long jami = 0;
for (int i = 1; i <= 1000; i++) {
jami += (long) i * v->raqam;
}
v->natija = jami;
return NULL;
}
int main(void) {
pthread_t oqimlar[OQIMLAR];
Vazifa vazifalar[OQIMLAR];
for (int i = 0; i < OQIMLAR; i++) {
vazifalar[i].raqam = i + 1;
vazifalar[i].natija = 0;
if (pthread_create(&oqimlar[i], NULL, yigindi_hisobla, &vazifalar[i]) != 0) {
return 1;
}
}
for (int i = 0; i < OQIMLAR; i++) {
pthread_join(oqimlar[i], NULL);
}
for (int i = 0; i < OQIMLAR; i++) {
printf("Oqim %d natijasi: %ld\n", vazifalar[i].raqam, vazifalar[i].natija);
}
return 0;
}
Oqim 1 natijasi: 500500
Oqim 2 natijasi: 1001000
Oqim 3 natijasi: 1501500
Oqim 4 natijasi: 2002000
for (int i = 0; i < 4; i++) {
pthread_create(&oqimlar[i], NULL, ish, &i); /* XATO! */
}
Bu yerda barcha oqimlar bir xil i o'zgaruvchisiga ko'rsatadi.
Sikl davom etgani sari i o'zgaradi va oqimlar noto'g'ri qiymat
o'qiydi.
To'g'ri yo'l - yuqoridagi kabi har biriga alohida element berish:
pthread_create(&oqimlar[i], NULL, ish, &vazifalar[i]);
Poyga holati (race condition) #
#include <stdio.h>
#include <pthread.h>
#define OQIMLAR 4
#define QADAMLAR 100000
int umumiy_hisob = 0; /* himoyalanmagan! */
void *oshir(void *arg) {
(void) arg;
for (int i = 0; i < QADAMLAR; i++) {
umumiy_hisob++; /* xavfli */
}
return NULL;
}
int main(void) {
pthread_t oqimlar[OQIMLAR];
for (int i = 0; i < OQIMLAR; i++) {
pthread_create(&oqimlar[i], NULL, oshir, NULL);
}
for (int i = 0; i < OQIMLAR; i++) {
pthread_join(oqimlar[i], NULL);
}
int kutilgan = OQIMLAR * QADAMLAR;
printf("Kutilgan natija : %d\n", kutilgan);
printf("Natija to'g'rimi? %s\n", umumiy_hisob == kutilgan ? "ha" : "yo'q");
return 0;
}
Kutilgan natija : 400000
Natija to'g'rimi? yo'q
Yuqoridagi dastur ba'zan to'g'ri natija berishi ham mumkin. Bu uni eng xavfli xato turiga aylantiradi:
- sinovda ishlaydi, ishlab chiqarishda buziladi;
- kuchsiz kompyuterda chiqmaydi, kuchli serverda chiqadi;
- nosozlik tuzatuvchi ostida yo'qoladi (Heisenbug).
Yechim - keyingi bo'limdagi sinxronizatsiya: mutex, atomik amallar yoki umumiy o'zgaruvchidan butunlay voz kechish.
Oqimlar nimani bo'lishadi? #
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int global_ozgaruvchi = 10;
void *ish(void *arg) {
(void) arg;
int mahalliy = 20; /* oqimning o'z stekida */
global_ozgaruvchi += 5; /* umumiy xotirada */
printf(" Oqim: mahalliy=%d global=%d\n", mahalliy, global_ozgaruvchi);
printf(" Oqim: PID = asosiy oqim bilan bir xilmi? %s\n",
getpid() == getppid() ? "yo'q" : "ha");
return NULL;
}
int main(void) {
pthread_t oqim;
pthread_create(&oqim, NULL, ish, NULL);
pthread_join(oqim, NULL);
printf("Asosiy: global = %d (oqim o'zgartirdi)\n", global_ozgaruvchi);
return 0;
}
Oqim: mahalliy=20 global=15
Oqim: PID = asosiy oqim bilan bir xilmi? ha
Asosiy: global = 15 (oqim o'zgartirdi)
| Bo'lishiladi | Har oqimda alohida |
|---|---|
| Kod (text) | Stek |
| Global va statik o'zgaruvchilar | Registrlar |
Uyum (malloc bilan olingan) | Dastur hisoblagichi |
| Ochiq fayl deskriptorlari | Oqim identifikatori |
| Joriy papka, PID | errno |
| Signal ishlovchilari | Ustuvorlik |
Oqimdan natija qaytarish #
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *kvadrat(void *arg) {
int son = *(int *) arg;
int *natija = malloc(sizeof(int));
if (natija == NULL) {
return NULL;
}
*natija = son * son;
return natija; /* chaqiruvchi free qiladi */
}
int main(void) {
pthread_t oqim;
int son = 7;
void *natija;
pthread_create(&oqim, NULL, kvadrat, &son);
pthread_join(oqim, &natija);
if (natija != NULL) {
printf("%d ning kvadrati: %d\n", son, *(int *) natija);
free(natija);
}
return 0;
}
7 ning kvadrati: 49
void *ish(void *arg) {
int natija = 42;
return &natija; /* XATO: oqim tugagach stek yo'qoladi */
}
Bu C darsligidagi «mahalliy o'zgaruvchi manzilini qaytarmang» qoidasining aynan o'zi.
Uch to'g'ri yo'l:
mallocbilan uyumda joy olish (yuqoridagi misol);- Natijani argument orqali berilgan tuzilmaga yozish;
- Global yoki statik o'zgaruvchi (lekin sinxronizatsiya kerak).
Ikkinchi usul eng xavfsiz - malloc/free juftligi ham kerak emas.
Ajratilgan oqim (detached) #
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *fon_ishi(void *arg) {
(void) arg;
printf(" Fon ishi bajarildi\n");
return NULL;
}
int main(void) {
pthread_t oqim;
pthread_create(&oqim, NULL, fon_ishi, NULL);
pthread_detach(oqim); /* join kerak emas */
sleep(1); /* oqim tugashiga vaqt beramiz */
printf("Asosiy oqim tugadi\n");
return 0;
}
Fon ishi bajarildi
Asosiy oqim tugadi
pthread_join chaqirilmasa - resurs sizadijoin qilinmagan oqim tugaganda, uning resurslari bo'shatilmaydi -
xuddi zombi jarayon kabi.
Ikki yechim:
pthread_join- oqimni kutasiz va natijasini olasiz;pthread_detach- «bu oqim natijasi kerak emas» deysiz, tizim uni o'zi tozalaydi.
Har bir oqim uchun ikkalasidan bittasi bo'lishi shart.
main tugasa - hamma oqim o'ladiint main(void) {
pthread_create(&oqim, NULL, uzoq_ish, NULL);
return 0; /* oqim yarim yo'lda o'ladi */
}
main funksiyasi tugashi butun jarayonni tugatadi. Barcha oqimlar
darhol to'xtaydi - tozalanish imkoni berilmasdan.
Shuning uchun yuqoridagi misolda sleep(1) bor. Amalda esa sleep
emas, pthread_join ishlatiladi - u ishonchli.
Oqim va jarayon tezligi #
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#define MARTA 300
void *bosh_ish(void *arg) {
(void) arg;
return NULL;
}
static double olchash_boshla(struct timespec *t) {
clock_gettime(CLOCK_MONOTONIC, t);
return 0.0;
}
static double olchash_tugat(const struct timespec *boshi) {
struct timespec hozir;
clock_gettime(CLOCK_MONOTONIC, &hozir);
return (double) (hozir.tv_sec - boshi->tv_sec) +
(double) (hozir.tv_nsec - boshi->tv_nsec) / 1e9;
}
int main(void) {
struct timespec t;
/* oqimlar */
olchash_boshla(&t);
for (int i = 0; i < MARTA; i++) {
pthread_t oqim;
pthread_create(&oqim, NULL, bosh_ish, NULL);
pthread_join(oqim, NULL);
}
double oqim_vaqti = olchash_tugat(&t);
/* jarayonlar */
olchash_boshla(&t);
for (int i = 0; i < MARTA; i++) {
pid_t bola = fork();
if (bola == 0) {
_exit(0);
}
wait(NULL);
}
double jarayon_vaqti = olchash_tugat(&t);
printf("%d marta yaratildi\n", MARTA);
printf("Oqim jarayondan tezroqmi? %s\n",
oqim_vaqti < jarayon_vaqti ? "ha" : "yo'q");
/* aniq raqamlar uchun:
printf(" oqim : %.6f soniya\n", oqim_vaqti);
printf(" jarayon : %.6f soniya\n", jarayon_vaqti);
*/
return 0;
}
300 marta yaratildi
Oqim jarayondan tezroqmi? ha
Bir sinovda natija shunday chiqdi:
oqim : 0.010421 soniya
jarayon : 0.087634 soniya
Oqim yaratish taxminan 8 barobar tezroq bo'ldi - chunki xotira xaritasini nusxalash shart emas.
Qachon oqim, qachon jarayon? #
| Vaziyat | Tanlov |
|---|---|
| Umumiy ma'lumot ustida tez ishlash | Oqim |
| Bir vazifani ko'p yadroga bo'lish | Oqim |
| Grafik interfeys + fon ishi | Oqim |
| Ishonchsiz kodni ishga tushirish | Jarayon |
| Xatolik butun tizimni buzmasin | Jarayon |
| Turli huquqlar kerak | Jarayon |
| Turli dasturni ishga tushirish | Jarayon (fork + exec) |
Brauzerlar jarayonni tanlaydi: har bir varaq alohida jarayon. Bir sayt qulasa, boshqa varaqlar tirik qoladi va saytlar bir-birining xotirasini o'qiy olmaydi.
Ma'lumotlar bazasi serverlari esa ko'pincha oqimni tanlaydi - umumiy kesh va tez almashinuv muhimroq.
Oqimlar sonini tanlash #
#include <stdio.h>
#include <unistd.h>
int main(void) {
long yadrolar = sysconf(_SC_NPROCESSORS_ONLN);
printf("Mavjud yadrolar aniqlandi: %s\n", yadrolar > 0 ? "ha" : "yo'q");
printf("Kamida bitta yadro bormi? %s\n", yadrolar >= 1 ? "ha" : "yo'q");
/* aniq sonni ko'rish uchun: */
/* printf("Yadrolar soni: %ld\n", yadrolar); */
return 0;
}
Mavjud yadrolar aniqlandi: ha
Kamida bitta yadro bormi? ha
| Ish turi | Tavsiya |
|---|---|
| Protsessorga bog'liq (hisoblash) | Yadrolar soniga teng |
| I/O ga bog'liq (disk, tarmoq) | Yadrolardan ancha ko'p |
Hisoblash ishida ortiqcha oqim foyda bermaydi - ular navbat kutadi va kontekst almashinuvi qo'shimcha yuk beradi.
I/O ishida esa oqimlar ko'pincha bloklangan holatda turadi, shuning uchun ularni ko'p yaratish mumkin.
Amalda esa har safar oqim yaratmasdan, oqimlar hovuzi (thread pool) ishlatiladi - oldindan yaratilgan oqimlar vazifalarni navbatdan oladi.
- Bitta oqim yaratib, unga matn chiqartiring.
- To'rt oqim yaratib, har biriga alohida raqam bering.
- Sikldagi
&ixatosini qilib, natijani kuzating. - Poyga holatini takrorlang va natija har safar o'zgarishini ko'ring.
QADAMLARni 100 ga kamaytiring - xato hali ham chiqadimi?- Oqimdan
mallocbilan natija qaytaring. - Mahalliy o'zgaruvchi manzilini qaytarib, nima bo'lishini ko'ring.
pthread_joinsiz oqim yaratib,mainni darhol tugating.pthread_detachbilan fon oqimini yarating.- Oqim va jarayon yaratish tezligini o'z kompyuteringizda o'lchang.
Xulosa #
- Oqim - bitta jarayon ichidagi mustaqil bajarilish yo'li.
- Oqimlar xotirani bo'lishadi, faqat stek har birida alohida.
- Oqim yaratish jarayon yaratishdan ancha tezroq.
- Kompilyatsiyada
-pthreadbayrog'i kerak. - Har bir oqimga alohida ma'lumot bering - sikl o'zgaruvchisi manzilini emas.
- Umumiy o'zgaruvchini himoyasiz o'zgartirish - poyga holati.
hisob++aslida uch amal, shuning uchun u atomik emas.- Poyga holati ba'zan to'g'ri natija beradi - eng xavfli xato turi.
- Har bir oqim uchun
joinyokidetachbo'lishi shart. maintugasa barcha oqimlar darhol o'ladi.- Ishonchsiz kod uchun jarayon, umumiy ma'lumot uchun oqim.
Keyingi bo'limda protsessor vaqti qanday taqsimlanishini - rejalashtirish algoritmlarini ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.