JavaScript-da haqiqiy ko'p oqimlilikni oching. Ushbu qo'llanma SharedArrayBuffer, Atomics, Web Workers va yuqori samarali veb-ilovalarga qo'yiladigan xavfsizlik talablarini qamrab oladi.
JavaScript SharedArrayBuffer: Vebda parallel dasturlashga chuqur kirish
O'n yillar davomida JavaScript-ning bir oqimli tabiati uning soddaligi manbai va ayni paytda jiddiy ishlash samaradorligidagi to'siq bo'lib kelgan. Hodisalar tsikli modeli ko'pchilik foydalanuvchi interfeysiga asoslangan vazifalar uchun ajoyib ishlaydi, ammo hisoblash talab qiladigan intensiv operatsiyalarga duch kelganda qiynaladi. Uzoq davom etadigan hisob-kitoblar brauzerni muzlatib qo'yishi mumkin, bu esa foydalanuvchi uchun noqulay tajriba yaratadi. Web Workers skriptlarning fonda ishlashiga imkon berib, qisman yechim taklif qilgan bo'lsa-da, ularning o'ziga xos katta cheklovi bor edi: ma'lumotlar almashinuvining samarasizligi.
Vebda oqimlar o'rtasida haqiqiy, past darajadagi xotirani bo'lishishni joriy etish orqali o'yin qoidalarini tubdan o'zgartiradigan kuchli xususiyat - SharedArrayBuffer
(SAB) ga kiring. Atomics
ob'ekti bilan birgalikda SAB to'g'ridan-to'g'ri brauzerda yuqori samarali, parallel ilovalarning yangi davrini ochib beradi. Biroq, katta kuch bilan birga katta mas'uliyat va murakkablik ham keladi.
Ushbu qo'llanma sizni JavaScript-dagi parallel dasturlash olamiga chuqur olib kiradi. Biz uning nima uchun kerakligini, SharedArrayBuffer
va Atomics
qanday ishlashini, siz hal qilishingiz kerak bo'lgan muhim xavfsizlik masalalarini va boshlashingiz uchun amaliy misollarni o'rganamiz.
Eski dunyo: JavaScript-ning bir oqimli modeli va uning cheklovlari
Yechimni qadrlashdan oldin, biz muammoni to'liq tushunishimiz kerak. Brauzerdagi JavaScript an'anaviy ravishda bitta oqimda, ko'pincha "asosiy oqim" yoki "UI oqimi" deb ataladigan oqimda bajariladi.
Hodisalar tsikli
Asosiy oqim hamma narsa uchun mas'uldir: JavaScript kodingizni bajarish, sahifani render qilish, foydalanuvchi harakatlariga (bosish va aylantirish kabi) javob berish va CSS animatsiyalarini ishga tushirish. U bu vazifalarni hodisalar tsikli yordamida boshqaradi, bu esa doimiy ravishda xabarlar (vazifalar) navbatini qayta ishlaydi. Agar vazifani bajarish uchun ko'p vaqt talab etilsa, u butun navbatni bloklaydi. Boshqa hech narsa sodir bo'lmaydi — UI muzlaydi, animatsiyalar to'xtab qoladi va sahifa javob bermay qo'yadi.
Web Workers: To'g'ri yo'nalishdagi qadam
Web Workers ushbu muammoni yumshatish uchun joriy qilingan. Web Worker mohiyatan alohida fon oqimida ishlaydigan skriptdir. Siz og'ir hisob-kitoblarni workerga yuklashingiz mumkin, bu esa asosiy oqimni foydalanuvchi interfeysini boshqarish uchun bo'sh qoldiradi.
Asosiy oqim va worker o'rtasidagi aloqa postMessage()
API orqali amalga oshiriladi. Ma'lumotlarni yuborganingizda, u tuzilmaviy klonlash algoritmi tomonidan qayta ishlanadi. Bu ma'lumotlar seriyalashtiriladi, nusxalanadi va keyin worker kontekstida deseriyalashtiriladi degan ma'noni anglatadi. Samarali bo'lishiga qaramay, bu jarayon katta hajmdagi ma'lumotlar uchun jiddiy kamchiliklarga ega:
- Samaradorlikka qo'shimcha yuklama: Oqimlar o'rtasida megabaytlar yoki hatto gigabaytlar ma'lumotni nusxalash sekin va protsessorni ko'p talab qiladi.
- Xotira iste'moli: Bu xotirada ma'lumotlarning dublikatini yaratadi, bu esa xotirasi cheklangan qurilmalar uchun katta muammo bo'lishi mumkin.
Brauzerda video tahrirlovchini tasavvur qiling. Butun bir video kadrni (bir necha megabayt bo'lishi mumkin) soniyasiga 60 marta qayta ishlash uchun workerga yuborib, qaytarib olish juda qimmatga tushadi. Bu aynan SharedArrayBuffer
hal qilish uchun yaratilgan muammodir.
O'yinni o'zgartiruvchi: SharedArrayBuffer
taqdimoti
SharedArrayBuffer
— bu ArrayBuffer
ga o'xshash, qat'iy uzunlikdagi xom ikkilik ma'lumotlar buferidir. Eng muhim farq shundaki, SharedArrayBuffer
bir nechta oqimlar (masalan, asosiy oqim va bir yoki bir nechta Web Workers) o'rtasida bo'lishilishi mumkin. Siz postMessage()
yordamida SharedArrayBuffer
ni "yuborganingizda", siz nusxani emas, balki bir xil xotira blokiga havolani yuborasiz.
Bu shuni anglatadiki, bufer ma'lumotlariga bir oqim tomonidan kiritilgan har qanday o'zgartirishlar unga havolasi bo'lgan boshqa barcha oqimlarga bir zumda ko'rinadi. Bu qimmatga tushadigan nusxalash va seriyalashtirish bosqichini yo'q qiladi, deyarli bir zumda ma'lumotlar almashinuvini ta'minlaydi.
Buni quyidagicha tasavvur qiling:
postMessage()
bilan Web Workers: Bu ikki hamkasbning hujjat ustida bir-biriga nusxalarini elektron pochta orqali yuborib ishlashiga o'xshaydi. Har bir o'zgartirish butunlay yangi nusxani yuborishni talab qiladi.SharedArrayBuffer
bilan Web Workers: Bu ikki hamkasbning bir xil hujjat ustida umumiy onlayn tahrirlovchida (Google Docs kabi) ishlashiga o'xshaydi. O'zgarishlar ikkalasiga ham real vaqtda ko'rinadi.
Umumiy xotira xavfi: Poyga holatlari
Bir zumda xotirani bo'lishish kuchli, ammo u parallel dasturlash olamidagi klassik muammoni ham keltirib chiqaradi: poyga holatlari.
Poyga holati bir nechta oqimlar bir vaqtning o'zida bir xil umumiy ma'lumotlarga kirishga va ularni o'zgartirishga harakat qilganda yuzaga keladi va yakuniy natija ularning bajarilishining oldindan aytib bo'lmaydigan tartibiga bog'liq bo'ladi. SharedArrayBuffer
da saqlangan oddiy hisoblagichni ko'rib chiqaylik. Ham asosiy oqim, ham worker uni oshirmoqchi.
- A oqimi joriy qiymatni o'qiydi, u 5 ga teng.
- A oqimi yangi qiymatni yozishdan oldin, operatsion tizim uni to'xtatib, B oqimiga o'tadi.
- B oqimi joriy qiymatni o'qiydi, u hali ham 5 ga teng.
- B oqimi yangi qiymatni (6) hisoblab, uni xotiraga qayta yozadi.
- Tizim yana A oqimiga qaytadi. U B oqimining biror narsa qilganini bilmaydi. U to'xtagan joyidan davom etib, o'zining yangi qiymatini (5 + 1 = 6) hisoblab, 6 ni xotiraga qayta yozadi.
Hisoblagich ikki marta oshirilgan bo'lsa ham, yakuniy qiymat 7 emas, balki 6. Operatsiyalar atomar emas edi — ular to'xtatilishi mumkin edi, bu esa ma'lumotlarning yo'qolishiga olib keldi. Aynan shuning uchun siz SharedArrayBuffer
ni uning muhim hamkori bo'lmagan holda ishlata olmaysiz: Atomics
ob'ekti.
Umumiy xotira qo'riqchisi: Atomics
ob'ekti
Atomics
ob'ekti SharedArrayBuffer
ob'ektlarida atomar operatsiyalarni bajarish uchun statik usullar to'plamini taqdim etadi. Atomar operatsiya boshqa hech qanday operatsiya tomonidan to'xtatilmasdan to'liq bajarilishi kafolatlanadi. U yoki to'liq sodir bo'ladi, yoki umuman sodir bo'lmaydi.
Atomics
dan foydalanish umumiy xotiradagi o'qish-o'zgartirish-yozish operatsiyalarining xavfsiz bajarilishini ta'minlash orqali poyga holatlarining oldini oladi.
Asosiy Atomics
usullari
Keling, Atomics
tomonidan taqdim etilgan eng muhim usullardan ba'zilarini ko'rib chiqaylik.
Atomics.load(typedArray, index)
: Berilgan indeksdagi qiymatni atomar ravishda o'qiydi va uni qaytaradi. Bu sizning to'liq, buzilmagan qiymatni o'qiyotganingizni ta'minlaydi.Atomics.store(typedArray, index, value)
: Berilgan indeksga qiymatni atomar ravishda saqlaydi va o'sha qiymatni qaytaradi. Bu yozish operatsiyasining to'xtatilmasligini ta'minlaydi.Atomics.add(typedArray, index, value)
: Berilgan indeksdagi qiymatga qiymatni atomar ravishda qo'shadi. U o'sha pozitsiyadagi asl qiymatni qaytaradi. Bux += value
ning atomar ekvivalenti.Atomics.sub(typedArray, index, value)
: Berilgan indeksdagi qiymatdan qiymatni atomar ravishda ayiradi.Atomics.compareExchange(typedArray, index, expectedValue, replacementValue)
: Bu kuchli shartli yozishdir. Uindex
dagi qiymatningexpectedValue
ga tengligini tekshiradi. Agar shunday bo'lsa, unireplacementValue
bilan almashtiradi va aslexpectedValue
ni qaytaradi. Aks holda, u hech narsa qilmaydi va joriy qiymatni qaytaradi. Bu blokirovkalar kabi murakkabroq sinxronizatsiya primitivlarini amalga oshirish uchun asosiy qurilish blokidir.
Sinxronizatsiya: Oddiy operatsiyalardan tashqari
Ba'zan sizga faqat xavfsiz o'qish va yozishdan ko'proq narsa kerak bo'ladi. Sizga oqimlarning bir-birini muvofiqlashtirishi va kutishi kerak bo'ladi. Keng tarqalgan antipattern "band kutish" dir, bunda oqim qisqa tsiklda o'tirib, doimiy ravishda xotira joyini o'zgarish uchun tekshiradi. Bu protsessor tsikllarini isrof qiladi va batareya quvvatini kamaytiradi.
Atomics
wait()
va notify()
bilan ancha samaraliroq yechimni taqdim etadi.
Atomics.wait(typedArray, index, value, timeout)
: Bu oqimga uxlashni aytadi. Uindex
dagi qiymatning hali hamvalue
ekanligini tekshiradi. Agar shunday bo'lsa, oqimAtomics.notify()
tomonidan uyg'otilguncha yoki ixtiyoriytimeout
(millisekundlarda) yetguncha uxlaydi. Agarindex
dagi qiymat allaqachon o'zgargan bo'lsa, u darhol qaytadi. Bu juda samarali, chunki uxlayotgan oqim deyarli hech qanday protsessor resurslarini iste'mol qilmaydi.Atomics.notify(typedArray, index, count)
: BuAtomics.wait()
orqali ma'lum bir xotira joyida uxlayotgan oqimlarni uyg'otish uchun ishlatiladi. U ko'pi bilancount
ta kutayotgan oqimni uyg'otadi (yokicount
berilmagan yokiInfinity
bo'lsa, ularning barchasini).
Barchasini birlashtirish: Amaliy qo'llanma
Nazariyani tushunganimizdan so'ng, keling, SharedArrayBuffer
yordamida yechimni amalga oshirish bosqichlarini ko'rib chiqaylik.
1-qadam: Xavfsizlik uchun zaruriy shart - Kelib chiqishi har xil bo'lgan izolyatsiya
Bu dasturchilar uchun eng ko'p uchraydigan to'siqdir. Xavfsizlik nuqtai nazaridan, SharedArrayBuffer
faqat kelib chiqishi har xil bo'lgan izolyatsiya qilingan holatdagi sahifalarda mavjud. Bu Spectre kabi spekulyativ bajarilish zaifliklarini yumshatish uchun xavfsizlik chorasi bo'lib, ular potensial ravishda yuqori aniqlikdagi taymerlardan (umumiy xotira tufayli mumkin bo'lgan) foydalanib, turli manbalar o'rtasida ma'lumotlarni sizdirishi mumkin.
Kelib chiqishi har xil bo'lgan izolyatsiyani yoqish uchun siz veb-serveringizni asosiy hujjatingiz uchun ikkita maxsus HTTP sarlavhasini yuborishga sozlashingiz kerak:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
(COOP): Hujjatingizning ko'rish kontekstini boshqa hujjatlardan izolyatsiya qiladi, ularning sizning window ob'ektingiz bilan bevosita o'zaro ta'sirini oldini oladi.Cross-Origin-Embedder-Policy: require-corp
(COEP): Sahifangiz tomonidan yuklangan barcha quyi resurslar (rasmlar, skriptlar va iframe'lar kabi) bir xil manbadan bo'lishi yokiCross-Origin-Resource-Policy
sarlavhasi yoki CORS bilan aniq kelib chiqishi har xil bo'lgan yuklanadigan deb belgilanishi kerakligini talab qiladi.
Buni sozlash qiyin bo'lishi mumkin, ayniqsa, agar siz zarur sarlavhalarni taqdim etmaydigan uchinchi tomon skriptlari yoki resurslariga tayansangiz. Serveringizni sozlaganingizdan so'ng, brauzer konsolida self.crossOriginIsolated
xususiyatini tekshirish orqali sahifangiz izolyatsiya qilinganligini tasdiqlashingiz mumkin. U true
bo'lishi kerak.
2-qadam: Buferni yaratish va bo'lishish
Asosiy skriptingizda siz SharedArrayBuffer
va uning ustida Int32Array
kabi TypedArray
yordamida "ko'rinish" yaratasiz.
main.js:
// Avval kelib chiqishi har xil bo'lgan izolyatsiyani tekshiring!
if (!self.crossOriginIsolated) {
console.error("Ushbu sahifa kelib chiqishi har xil bo'lgan izolyatsiya qilinmagan. SharedArrayBuffer mavjud bo'lmaydi.");
} else {
// Bitta 32-bitli butun son uchun umumiy bufer yarating.
const buffer = new SharedArrayBuffer(4);
// Buferda ko'rinish yarating. Barcha atomar operatsiyalar ko'rinishda sodir bo'ladi.
const int32Array = new Int32Array(buffer);
// 0-indeksdagi qiymatni ishga tushiring.
int32Array[0] = 0;
// Yangi worker yarating.
const worker = new Worker('worker.js');
// UMUMIY buferni workerga yuboring. Bu nusxa emas, havola uzatishdir.
worker.postMessage({ buffer });
// Workerdan keladigan xabarlarni tinglang.
worker.onmessage = (event) => {
console.log(`Worker bajarilganligi haqida xabar berdi. Yakuniy qiymat: ${Atomics.load(int32Array, 0)}`);
};
}
3-qadam: Workerda atomar operatsiyalarni bajarish
Worker buferni qabul qiladi va endi unda atomar operatsiyalarni bajarishi mumkin.
worker.js:
self.onmessage = (event) => {
const { buffer } = event.data;
const int32Array = new Int32Array(buffer);
console.log("Worker umumiy buferni qabul qildi.");
// Keling, ba'zi atomar operatsiyalarni bajaramiz.
for (let i = 0; i < 1000000; i++) {
// Umumiy qiymatni xavfsiz ravishda oshiring.
Atomics.add(int32Array, 0, 1);
}
console.log("Worker oshirishni tugatdi.");
// Tugatganimiz haqida asosiy oqimga signal bering.
self.postMessage({ done: true });
};
4-qadam: Murakkabroq misol - Sinxronizatsiya bilan parallel yig'indini hisoblash
Keling, yanada real muammoni hal qilaylik: bir nechta worker yordamida juda katta sonlar massivini yig'ish. Biz samarali sinxronizatsiya uchun Atomics.wait()
va Atomics.notify()
dan foydalanamiz.
Bizning umumiy buferimiz uch qismdan iborat bo'ladi:
- Indeks 0: Status bayrog'i (0 = qayta ishlanmoqda, 1 = tugallangan).
- Indeks 1: Qancha worker tugatganligi uchun hisoblagich.
- Indeks 2: Yakuniy yig'indi.
main.js:
if (self.crossOriginIsolated) {
const NUM_WORKERS = 4;
const DATA_SIZE = 10_000_000;
// [status, workers_finished, result_low, result_high]
// Katta yig'indilar uchun to'lib ketishning oldini olish uchun natija uchun ikkita 32-bitli butun sondan foydalanamiz.
const sharedBuffer = new SharedArrayBuffer(4 * 4); // 4 ta butun son
const sharedArray = new Int32Array(sharedBuffer);
// Qayta ishlash uchun ba'zi tasodifiy ma'lumotlarni yarating
const data = new Uint8Array(DATA_SIZE);
for (let i = 0; i < DATA_SIZE; i++) {
data[i] = Math.floor(Math.random() * 10);
}
const chunkSize = Math.ceil(DATA_SIZE / NUM_WORKERS);
for (let i = 0; i < NUM_WORKERS; i++) {
const worker = new Worker('sum_worker.js');
const start = i * chunkSize;
const end = Math.min(start + chunkSize, DATA_SIZE);
// Workerning ma'lumotlar qismi uchun umumiy bo'lmagan ko'rinish yarating
const dataChunk = data.subarray(start, end);
worker.postMessage({
sharedBuffer,
dataChunk // Bu nusxalanadi
});
}
console.log('Asosiy oqim endi workerlarning tugashini kutmoqda...');
// 0-indeksdagi status bayrog'ining 1 bo'lishini kuting
// Bu while tsiklidan ancha yaxshi!
Atomics.wait(sharedArray, 0, 0); // Agar sharedArray[0] 0 bo'lsa kuting
console.log('Asosiy oqim uyg'ondi!');
const finalSum = Atomics.load(sharedArray, 2);
console.log(`Yakuniy parallel yig'indi: ${finalSum}`);
} else {
console.error('Sahifa kelib chiqishi har xil bo\'lgan izolyatsiya qilinmagan.');
}
sum_worker.js:
self.onmessage = ({ data }) => {
const { sharedBuffer, dataChunk } = data;
const sharedArray = new Int32Array(sharedBuffer);
// Bu workerning bo'lagi uchun yig'indini hisoblang
let localSum = 0;
for (let i = 0; i < dataChunk.length; i++) {
localSum += dataChunk[i];
}
// Mahalliy yig'indini umumiy yig'indiga atomar ravishda qo'shing
Atomics.add(sharedArray, 2, localSum);
// 'tugatgan workerlar' hisoblagichini atomar ravishda oshiring
const finishedCount = Atomics.add(sharedArray, 1, 1) + 1;
// Agar bu tugatgan oxirgi worker bo'lsa...
const NUM_WORKERS = 4; // Haqiqiy ilovada uzatilishi kerak
if (finishedCount === NUM_WORKERS) {
console.log('Oxirgi worker tugatdi. Asosiy oqimga xabar berilmoqda.');
// 1. Status bayrog'ini 1 (tugallangan) ga o'rnating
Atomics.store(sharedArray, 0, 1);
// 2. 0-indeksda kutayotgan asosiy oqimga xabar bering
Atomics.notify(sharedArray, 0, 1);
}
};
Haqiqiy dunyodagi qo'llash sohalari va ilovalar
Ushbu kuchli, ammo murakkab texnologiya aslida qayerda o'zgarish yasaydi? U katta ma'lumotlar to'plamlarida og'ir, parallellashtirilishi mumkin bo'lgan hisob-kitoblarni talab qiladigan ilovalarda ustunlik qiladi.
- WebAssembly (Wasm): Bu eng asosiy qo'llash sohasi. C++, Rust va Go kabi tillar ko'p oqimlilikni yetuk qo'llab-quvvatlaydi. Wasm dasturchilarga ushbu mavjud yuqori samarali, ko'p oqimli ilovalarni (o'yin dvijoklari, SAPR dasturlari va ilmiy modellar kabi) brauzerda ishlashi uchun kompilyatsiya qilish imkonini beradi, bunda oqimlararo aloqa uchun asosiy mexanizm sifatida
SharedArrayBuffer
dan foydalaniladi. - Brauzer ichida ma'lumotlarni qayta ishlash: Katta hajmdagi ma'lumotlarni vizualizatsiya qilish, mijoz tomonida mashinani o'rganish modellarini ishga tushirish va katta hajmdagi ma'lumotlarni qayta ishlaydigan ilmiy simulyatsiyalarni sezilarli darajada tezlashtirish mumkin.
- Media tahrirlash: Yuqori aniqlikdagi tasvirlarga filtrlarni qo'llash yoki ovoz faylida audio qayta ishlashni amalga oshirish qismlarga bo'linishi va bir nechta workerlar tomonidan parallel ravishda qayta ishlanishi mumkin, bu esa foydalanuvchiga real vaqtda fikr-mulohaza beradi.
- Yuqori samarali o'yinlar: Zamonaviy o'yin dvijoklari fizika, sun'iy intellekt va resurslarni yuklash uchun ko'p oqimlilikka qattiq tayanadi.
SharedArrayBuffer
to'liq brauzerda ishlaydigan konsol sifatidagi o'yinlarni yaratish imkonini beradi.
Qiyinchiliklar va yakuniy mulohazalar
SharedArrayBuffer
o'zgartiruvchi bo'lsa-da, u hamma muammolarni hal qiluvchi vosita emas. Bu ehtiyotkorlik bilan ishlashni talab qiladigan past darajadagi vositadir.
- Murakkablik: Parallel dasturlash juda qiyinligi bilan mashhur. Poyga holatlari va deadloklarni tuzatish nihoyatda qiyin bo'lishi mumkin. Siz ilovangiz holati qanday boshqarilishi haqida boshqacha fikr yuritishingiz kerak.
- Deadloklar: Deadlok ikki yoki undan ortiq oqim abadiy bloklanganda, har biri boshqasining resursni bo'shatishini kutganda yuzaga keladi. Bu murakkab blokirovka mexanizmlarini noto'g'ri amalga oshirsangiz sodir bo'lishi mumkin.
- Xavfsizlikka qo'shimcha yuklama: Kelib chiqishi har xil bo'lgan izolyatsiya talabi jiddiy to'siqdir. Agar ular zarur CORS/CORP sarlavhalarini qo'llab-quvvatlamasa, uchinchi tomon xizmatlari, reklamalar va to'lov shlyuzlari bilan integratsiyalarni buzishi mumkin.
- Har bir muammo uchun emas: Oddiy fon vazifalari yoki I/O operatsiyalari uchun
postMessage()
bilan an'anaviy Web Worker modeli ko'pincha soddaroq va yetarli.SharedArrayBuffer
ga faqat katta hajmdagi ma'lumotlarni o'z ichiga olgan aniq, protsessorga bog'liq to'siq bo'lgandagina murojaat qiling.
Xulosa
SharedArrayBuffer
, Atomics
va Web Workers bilan birgalikda veb-dasturlash uchun paradigma o'zgarishini anglatadi. U bir oqimli modelning chegaralarini buzib, brauzerga kuchli, samarali va murakkab ilovalarning yangi sinfini taklif qiladi. U hisoblash talab qiladigan intensiv vazifalar uchun veb-platformani mahalliy ilovalarni ishlab chiqish bilan tengroq darajaga qo'yadi.
Parallel JavaScript olamiga sayohat qiyin bo'lib, holatni boshqarish, sinxronizatsiya va xavfsizlikka qat'iy yondashuvni talab qiladi. Ammo vebda mumkin bo'lgan narsalarning chegaralarini kengaytirmoqchi bo'lgan dasturchilar uchun — real vaqtda audio sintezidan tortib murakkab 3D renderlash va ilmiy hisoblashlargacha — SharedArrayBuffer
ni o'zlashtirish endi shunchaki tanlov emas; bu veb-ilovalarning keyingi avlodini yaratish uchun zaruriy mahoratdir.