O'zbek

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:

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 ArrayBufferga 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 SharedArrayBufferni "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:

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. SharedArrayBufferda saqlangan oddiy hisoblagichni ko'rib chiqaylik. Ham asosiy oqim, ham worker uni oshirmoqchi.

  1. A oqimi joriy qiymatni o'qiydi, u 5 ga teng.
  2. A oqimi yangi qiymatni yozishdan oldin, operatsion tizim uni to'xtatib, B oqimiga o'tadi.
  3. B oqimi joriy qiymatni o'qiydi, u hali ham 5 ga teng.
  4. B oqimi yangi qiymatni (6) hisoblab, uni xotiraga qayta yozadi.
  5. 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 SharedArrayBufferni 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.

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.

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

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:

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.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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. SharedArrayBufferga 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 — SharedArrayBufferni o'zlashtirish endi shunchaki tanlov emas; bu veb-ilovalarning keyingi avlodini yaratish uchun zaruriy mahoratdir.