IlmHamroh
JavaScript Full-stack/13-qism. Brauzer API'lari, performans va xavfsizlik20/32-dars25 daqiqa
Mundarija (39)

JavaScript SharedArrayBuffer va Atomics: worker'lar orasida umumiy xotira

Qisqacha: Oddiy postMessage ma'lumotni worker'ga nusxalaydi. SharedArrayBuffer esa bitta xotirani bir nechta thread'ga umumiy qiladi: biri yozsa, boshqasi darhol ko'radi. Umumiy xotirada counter[0]++ kabi oddiy amal yangilanishlarni yo'qotadi (race condition) — shuning uchun Atomics.add, Atomics.wait va Atomics.notify ishlatiladi. Brauzerda SharedArrayBuffer faqat cross-origin isolation yoqilgan sahifada bor: server Cross-Origin-Opener-Policy va Cross-Origin-Embedder-Policy sarlavhalarini yuborishi kerak.

Bu darsda

  • Nusxa, ko'chirish (transfer) va umumiy xotira farqini tushuntira olasiz.
  • Umumiy hisoblagichda yangilanish qanday yo'qolishini ko'rasiz va uni Atomics bilan tuzatasiz.
  • Bir thread'ni Atomics.wait bilan uxlatib, boshqasidan Atomics.notify bilan uyg'ota olasiz; asosiy thread'da esa Atomics.waitAsync ishlatasiz.
  • Sahifani cross-origin isolation rejimiga o'tkazasiz: COOP va COEP sarlavhalari, ularning narxi va DevTools'da tekshirish.

Oldin bilishingiz kerak: Web Workers, Typed arrays va binar ma'lumot, Nozik xatolar: race condition, Origin va Same-Origin Policy.

1. Nega bu kerak?

O'tgan darsda og'ir hisobni Web Worker ga chiqardik. Sahifa va worker postMessage bilan gaplashdi. Har xabar nusxa bo'lib boradi (structured clone) yoki xotira butunlay ko'chiriladi (transfer) — shunda eski egasi uni yo'qotadi.

Ko'p holatda shu yetarli. Lekin ba'zi ishlarda ikkalasi ham noqulay. «Bahor» oshxonasida to'rt oshpaz bor va hammasi bitta doskaga qarab ishlaydi: "nechta buyurtma tayyor", "kim qaysi qozonda". Har oshpazga doskaning nusxasini berib chiqsangiz — ular bir-birining yozuvini ko'rmaydi. Doskani bittasidan olib boshqasiga berib yuborsangiz — qolganlari doskasiz qoladi.

Kerakli narsa — hamma bir vaqtda ko'radigan bitta doska. Dasturda u umumiy xotira (shared memory) deyiladi. JavaScript'da uning nomi — SharedArrayBuffer.

Bu chuqur mavzu. Kundalik saytlarda kam yoziladi. Lekin brauzerdagi video tahrirlagichlar (masalan, ffmpeg.wasm), Figma, Google Earth, brauzer o'yinlari aynan shu imkoniyatga tayanadi. Bunday kutubxonani ulaganingizda u "sahifangiz cross-origin isolated emas" deb xato beradi. Shu darsdan keyin bu xato nimani anglatishini va qanday tuzatilishini bilasiz.

2. Umumiy xotira: SharedArrayBuffer

2.1 Node'da sinab ko'ramiz

Brauzerda SharedArrayBuffer uchun maxsus server sarlavhalari kerak — ularni darsning oxirida ko'ramiz. Avval g'oyani Node'da sinaymiz: u yerda hech qanday sozlamasiz ishlaydi.

Node'da Web Worker'ning egizagi bor — node:worker_threads moduli. Uchta nom kerak bo'ladi:

  • new Worker(fayl, { workerData }) — yangi thread ochadi va unga boshlang'ich ma'lumot beradi.
  • isMainThread — true bo'lsa, kod asosiy thread'da, false bo'lsa — worker ichida ishlayapti.
  • workerData — worker ichida o'sha boshlang'ich ma'lumot.

Bitta fayl o'zini worker sifatida ham ishga tushiradi: new URL(import.meta.url) — shu faylning o'z manzili. Endi oddiy ArrayBuffer va SharedArrayBuffer ni yonma-yon beramiz. Worker ikkalasiga ham 42 yozadi:

js
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";
import { once } from "node:events";

if (isMainThread) {
  const copied = new Int32Array(new ArrayBuffer(4));
  const shared = new Int32Array(new SharedArrayBuffer(4));
  const worker = new Worker(new URL(import.meta.url), {
    workerData: { copied, shared },
  });
  await once(worker, "exit"); // worker tugashini kutamiz
  console.log("ArrayBuffer:", copied[0]);
  console.log("SharedArrayBuffer:", shared[0]);
} else {
  workerData.copied[0] = 42;
  workerData.shared[0] = 42;
}

Konsolda:

text
ArrayBuffer: 0
SharedArrayBuffer: 42

Oddiy bufer worker'ga nusxa bo'lib bordi — worker o'z nusxasiga yozdi, asosiy thread'dagi asl bufer o'zgarmadi. Umumiy bufer esa nusxalanmadi: ikkala thread bitta xotiraga qarab turibdi. once(worker, "exit") — worker tugaganda bajariladigan Promise (Promise yordamchilari dagi g'oya).

2.2 Uch yo'l — bitta rasmda

Diagrammada uch holat. Nimaga qarang: nusxada ikkita xotira bor, ko'chirishda bitta, lekin egasi almashadi, umumiy xotirada esa bitta xotira va ikki ega.

flowchart LR
  subgraph N["Nusxa (postMessage)"]
    A1["Sahifa: xotira A"] -- "nusxa" --> A2["Worker: xotira A'"]
  end
  subgraph K["Ko'chirish (transfer)"]
    B1["Sahifa: bo'sh"] -. "berib yubordi" .-> B2["Worker: xotira B"]
  end
  subgraph U["Umumiy (SharedArrayBuffer)"]
    C1["Sahifa"] --> C3[("bitta xotira C")]
    C2["Worker"] --> C3
  end

SharedArrayBuffer ning o'zi faqat baytlar "qutisi" — u ArrayBuffer ga o'xshaydi. Ichidagi sonlarni o'qish va yozish uchun unga typed array "ko'zoynagi" kiyiladi: new Int32Array(sab). Bufer o'sishi ham mumkin: new SharedArrayBuffer(4, { maxByteLength: 64 }) va keyin sab.grow(8). Lekin umumiy bufer kichraymaydi — boshqa thread uning oxirida ishlayotgan bo'lishi mumkin.

Tekshirib ko'ring: Worker'ga SharedArrayBuffer yuborildi, keyin asosiy thread unga 7 yozdi. Worker o'sha katakni o'qisa, nimani ko'radi? Oddiy ArrayBuffer bo'lsa-chi?

Javob

Umumiy buferda — 7 (ertami-kechmi; aniq qachonligi keyingi bo'limlarda). Oddiy buferda — yuborilgan paytdagi eski qiymat: worker o'z nusxasi bilan ishlaydi va asl buferdagi o'zgarishni hech qachon ko'rmaydi.

3. Race condition: yangilanish yo'qoladi

3.1 To'rt oshpaz, bitta hisoblagich

Endi to'rt worker bitta hisoblagichni oshiradi. Har biri million marta counter[0]++ qiladi. Kutilgan natija — 4 000 000:

js
// natija har safar boshqacha — shuning uchun tekshirilmaydi
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";
import { once } from "node:events";

const WORKERS = 4;
const TIMES = 1_000_000;

if (isMainThread) {
  const counter = new Int32Array(new SharedArrayBuffer(4));
  const workers = [];
  for (let i = 0; i < WORKERS; i++) {
    workers.push(new Worker(new URL(import.meta.url), {
      workerData: counter,
    }));
  }
  await Promise.all(workers.map((w) => once(w, "exit")));
  console.log(`Natija: ${counter[0]}, kutilgan: ${WORKERS * TIMES}`);
} else {
  const counter = workerData;
  for (let i = 0; i < TIMES; i++) counter[0]++;
}

Node 24 da besh marta ishga tushirdik (16 yadroli kompyuter):

text
Natija: 1078968, kutilgan: 4000000
Natija: 1989251, kutilgan: 4000000
Natija: 1954750, kutilgan: 4000000
Natija: 2071922, kutilgan: 4000000
Natija: 2569765, kutilgan: 4000000

Har safar boshqa son — va hammasi kam. Ba'zan yarmidan ham ko'p yangilanish yo'qoldi. Siz ham ishga tushiring: sonlar boshqacha chiqadi, lekin 4 000 000 deyarli hech qachon chiqmaydi.

3.2 Nega yo'qoladi

counter[0]++ bitta amalga o'xshaydi. Aslida u uch qadam: o'qish, bir qo'shish, yozish. Ikki oshpaz bir paytda o'qisa, ikkalasi bir xil eski sonni ko'radi. Ikkalasi +1 qilib yozadi — va ikki buyurtmadan bittasi yo'qoladi.

Bu holatni Nozik xatolar darsida "yo'qolgan yangilanish (lost update)" deb atagan edik. Haqiqiy thread'larni qadamma-qadam ko'rsatib bo'lmaydi. Shuning uchun quyida ikki oshpazning navbatini qo'lda yozdik — aynan shunday aralashuv protsessorda millionlab marta sodir bo'ladi:

Bitta threadli JavaScript'da bu muammo bo'lmagan: event loop bir paytda bitta kod bo'lagini bajaradi va counter++ hech qachon o'rtasida to'xtamaydi. Umumiy xotira bu kafolatni olib tashlaydi. Endi haqiqatan ham ikki thread bir vaqtda ishlaydi.

Tekshirib ko'ring: Worker'lar bitta emas, alohida kataklarni oshirsa (counter[0], counter[1], ...), natija to'g'ri chiqadimi?

Javob

Ha. Race condition faqat ikki thread bitta katakni o'qib-yozganda bo'ladi. Har kim o'z katagiga yozsa, to'qnashuv yo'q — oxirida asosiy thread kataklarni qo'shib chiqadi. Bu ko'pincha eng oddiy va tez yechim (2-mashqda shunday qilamiz).

4. Atomics: bo'linmas amallar

4.1 Tuzatish

Atomik (atomic) amal — boshqa thread o'rtasiga suqilib kira olmaydigan amal. "O'qish, qo'shish, yozish" bitta bo'linmas qadam bo'lib bajariladi. O'xshatish: doskadagi sonni o'chirib yangisini yozayotganda bo'r oshpazning qo'lidan tushmaydi va boshqa hech kim doskaga yaqinlasha olmaydi.

Global Atomics obyekti shunday amallarni beradi. Yuqoridagi siklda faqat bitta qator o'zgaradi:

js
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";
import { once } from "node:events";

const WORKERS = 4;
const TIMES = 1_000_000;

if (isMainThread) {
  const counter = new Int32Array(new SharedArrayBuffer(4));
  const workers = [];
  for (let i = 0; i < WORKERS; i++) {
    workers.push(new Worker(new URL(import.meta.url), {
      workerData: counter,
    }));
  }
  await Promise.all(workers.map((w) => once(w, "exit")));
  console.log(`Natija: ${counter[0]}, kutilgan: ${WORKERS * TIMES}`);
} else {
  const counter = workerData;
  for (let i = 0; i < TIMES; i++) Atomics.add(counter, 0, 1);
}

Konsolda:

text
Natija: 4000000, kutilgan: 4000000

Har safar aynan 4 000 000. Narxi — tezlik: atomik amal oddiy ++ dan sekinroq, chunki protsessor yadrolari o'zaro kelishib oladi. Shuning uchun Atomics faqat umumiy katakka bir nechta thread yozganda ishlatiladi.

4.2 Asosiy amallar

Hamma amal bir xil shaklda: Atomics.amal(typedArray, indeks, ...). Ularni bitta thread'da ham sinash mumkin — natija qiymatini ko'rish uchun:

js
const stock = new Int32Array(new SharedArrayBuffer(8));

Atomics.store(stock, 0, 10);          // osh: 10 porsiya
console.log(Atomics.add(stock, 0, 5));  // 10
console.log(Atomics.sub(stock, 0, 3));  // 15
console.log(Atomics.load(stock, 0));    // 12
console.log(Atomics.exchange(stock, 0, 0)); // 12
console.log(Atomics.compareExchange(stock, 1, 0, 7)); // 0
console.log(Atomics.compareExchange(stock, 1, 0, 9)); // 7
console.log(stock); // Int32Array(2) [ 0, 7 ]

Qoidani yodda tuting: o'zgartiradigan amallar eski qiymatni qaytaradi.

  • store / load — yozish va o'qish. Oddiy stock[0] = 10 dan farqi: boshqa thread yozuvni to'g'ri tartibda ko'rishi kafolatlanadi. Bu haqda "Ko'p uchraydigan xatolar" bo'limida.
  • add, sub — qo'shish va ayirish; and, or, xor — bitlar bilan xuddi shunday.
  • exchange — yangisini yozib, eskisini qaytaradi.
  • compareExchange(arr, i, kutilgan, yangi) — "katakda hali kutilgan turgan bo'lsa — yangi ni yoz". Birinchi chaqiruvda katak 0 edi — 7 yozildi. Ikkinchisida katak endi 7, 0 emas — hech narsa yozilmadi, 7 qaytdi. Qulf (lock) shu amal bilan yasaladi — 3-mashqda.

Atomics faqat butun sonli typed array bilan ishlaydi: Int8Array … BigInt64Array. Float64Array bersangiz:

js
const prices = new Float64Array(new SharedArrayBuffer(8));
Atomics.add(prices, 0, 1);
text
TypeError: [object Float64Array] is not an integer typed array.

Tarjimasi: "Float64Array — butun sonli typed array emas". Narxlarni so'mda butun son qilib saqlang (35000) — tiyin kerak bo'lsa, tiyinda.

Tekshirib ko'ring: Atomics.add(arr, 0, 5) qiymatni qaytardi: 3. Endi katakda nechchi?

Javob

8. add eski qiymatni (3) qaytaradi, katakka esa 3 + 5 yoziladi. Agar shu orada boshqa thread ham qo'shgan bo'lsa — undan ham ko'p; qaytgan 3 esa aynan sizning qo'shishingizdan oldingi holat.

5. Kutish va uyg'otish: wait va notify

5.1 Oshpaz buyurtmani kutadi

Worker'ga ish yo'q paytda nima qilsin? Siklda while (arr[0] === 0) {} qilib aylanib tursa — protsessor yadrosini 100% band qiladi va behuda isitadi. Yaxshisi — uxlab, kerak bo'lganda uyg'onish.

  • Atomics.wait(arr, i, qiymat) — "katakda hali qiymat bo'lsa — uxla". Thread to'xtaydi.
  • Atomics.notify(arr, i, soni) — shu katakni kutayotganlarni uyg'otadi (soni berilmasa — hammasini). Nechta thread uyg'onganini qaytaradi.

Oshpaz (worker) buyurtmani kutadi. Kassir (asosiy thread) 300 ms dan keyin buyurtma raqamini yozib, uni uyg'otadi:

js
import {
  Worker, isMainThread, parentPort, workerData,
} from "node:worker_threads";

// [0] — "tayyor" bayrog'i, [1] — buyurtma raqami
if (isMainThread) {
  const shared = new Int32Array(new SharedArrayBuffer(8));
  const worker = new Worker(new URL(import.meta.url), {
    workerData: shared,
  });
  worker.on("message", (text) => console.log(text));

  setTimeout(() => {
    Atomics.store(shared, 1, 17); // avval ma'lumot
    Atomics.store(shared, 0, 1); // keyin bayroq
    const woken = Atomics.notify(shared, 0);
    console.log(`Kassir: buyurtma yozildi, uyg'otildi: ${woken}`);
  }, 300);
} else {
  const shared = workerData;
  const result = Atomics.wait(shared, 0, 0); // 0 bo'lsa — uxla
  const order = Atomics.load(shared, 1);
  parentPort.postMessage(`Oshpaz: ${result}, buyurtma #${order}`);
}

Konsolda:

text
Kassir: buyurtma yozildi, uyg'otildi: 1
Oshpaz: ok, buyurtma #17

Qatorma-qator:

  • Worker Atomics.wait(shared, 0, 0) da to'xtadi: katak 0 — demak, uxlaydi. 300 ms davomida u protsessorni umuman ishlatmadi.
  • Kassir avval ma'lumotni (17), keyin bayroqni (1) yozdi. Tartib muhim: oshpaz uyg'onganda buyurtma raqami allaqachon joyida turadi.
  • notify bitta uxlayotganni uyg'otdi — 1 qaytardi. wait esa "ok" qaytardi.
  • parentPort.postMessage — Node worker'idan asosiy thread'ga xabar; brauzerdagi postMessage ning o'zi.

5.2 wait qaytaradigan uch qiymat

wait ga beriladigan qiymat — "men buni ko'rsam uxlayman" degani. Agar katakda allaqachon boshqa son bo'lsa, thread umuman uxlamaydi. To'rtinchi argument — ko'pi bilan necha millisekund kutish:

js
const flag = new Int32Array(new SharedArrayBuffer(4));

console.log(Atomics.wait(flag, 0, 7, 50)); // not-equal
console.log(Atomics.wait(flag, 0, 0, 50)); // timed-out
  • "not-equal" — katakda 7 emas, 0 turibdi. Uxlashning hojati yo'q: "kutgan narsam allaqachon o'zgargan".
  • "timed-out" — katak 0, thread uxladi, 50 ms ichida hech kim uyg'otmadi.
  • "ok" — uxladi va notify bilan uyg'otildi (oldingi misol).

"not-equal" muhim himoya. Kassir oshpazdan oldin ulgurib, bayroqni yozib notify qilsa nima bo'ladi? notify hech kimni topmaydi (0). Oshpaz kechikib wait ga kelganda katak allaqachon 1 — u uxlamaydi va buyurtmani darhol oladi. Agar wait qiymatni tekshirmaganda, oshpaz abadiy uxlab qolardi.

Bu blok js node deb belgilangan — saytdagi «Ishga tushir» tugmasi uni bajarmaydi. Sababi keyingi bo'limda.

Tekshirib ko'ring: Oshpaz wait ga yetib kelmasidan oldin kassir notify qildi. notify nechani qaytaradi va oshpazning wait i nimani qaytaradi?

Javob

notify — 0 (hali hech kim uxlamayapti). Oshpazning wait i — "not-equal": katakda allaqachon 1, u 0 ni kutgan edi. U uxlamasdan buyurtmani o'qiydi. Shuning uchun bayroq va qiymat tekshiruvi — wait/notify juftligining eng muhim qismi.

5.3 Asosiy thread'da — Atomics.waitAsync

Brauzer sahifasining asosiy thread'ida Atomics.wait taqiqlangan. Chrome 154 da sinadik:

text
TypeError: Atomics.wait cannot be called in this context

Tarjimasi: "Atomics.wait ni bu kontekstda chaqirib bo'lmaydi". Nega? Asosiy thread uxlasa, sahifa butunlay qotadi: tugmalar bosilmaydi, animatsiya to'xtaydi (Event loop). Node asosiy thread'ida wait ruxsat etilgan, chunki u yerda ekran yo'q.

Asosiy thread uchun — Atomics.waitAsync. U thread'ni to'xtatmaydi, Promise qaytaradi:

js
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";

if (isMainThread) {
  const shared = new Int32Array(new SharedArrayBuffer(4));
  new Worker(new URL(import.meta.url), { workerData: shared });

  const { async, value } = Atomics.waitAsync(shared, 0, 0);
  console.log("async:", async);
  console.log("Kassir kutmoqda, lekin bloklanmagan");
  console.log("Natija:", await value);
  console.log("Tayyor porsiyalar:", Atomics.load(shared, 0));
} else {
  setTimeout(() => {
    Atomics.store(workerData, 0, 3);
    Atomics.notify(workerData, 0);
  }, 200);
}

Konsolda:

text
async: true
Kassir kutmoqda, lekin bloklanmagan
Natija: ok
Tayyor porsiyalar: 3

waitAsync obyekt qaytaradi: async: true bo'lsa, value — Promise, u "ok" yoki "timed-out" bilan bajariladi. Katak allaqachon boshqa qiymatda bo'lsa — { async: false, value: "not-equal" }, Promise'siz. waitAsync Chrome 90 dan beri bor, Firefox'ga esa 145-versiyada keldi — web-features 3.40.0 bo'yicha 2025-yil noyabrdan Baseline (Newly available).

5.4 Qisqa aylanish: Atomics.pause

Ba'zan kutish juda qisqa — bir necha mikrosekund. Bunday paytda thread'ni uxlatish (wait) uyg'otishdan qimmatroq tushadi. Shunda bir oz "aylanib" turiladi:

js
// qulf bo'shashini qisqa kutish, keyin — uxlash
for (let i = 0; i < 100; i++) {
  if (Atomics.load(lock, 0) === 0) break;
  Atomics.pause(); // protsessorga: "men shunchaki kutyapman"
}

Atomics.pause() hech narsa qaytarmaydi va hech narsani o'zgartirmaydi. U protsessorga "bu aylanish — kutish" deb ishora beradi: yadro energiyani tejaydi, qo'shni thread'ga joy bo'shaydi. ES2026 standartida; web-features bo'yicha 2025-yil apreldan barcha asosiy brauzerlarda bor (Chrome 133, Firefox 137, Safari 18.4), Node 24 da ham ishlaydi.

6. Brauzerda: cross-origin isolation

6.1 Natija oynasida SharedArrayBuffer yo'q

Saytdagi natija oynasida tekshirib ko'ring:

html
<p>Konsolga qarang.</p>
<script>
  console.log("crossOriginIsolated:", crossOriginIsolated);
  console.log("SharedArrayBuffer:", typeof SharedArrayBuffer);
</script>

Konsolda:

text
crossOriginIsolated: false
SharedArrayBuffer: undefined

Konstruktor umuman yo'q — typeof "undefined" beradi. Sizning oddiy Live Server sahifangizda ham xuddi shunday bo'ladi. Worker'ga umumiy xotira yuborishga urinsangiz, Chrome shunday deydi:

text
DataCloneError: Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer requires self.crossOriginIsolated.

Tarjimasi: "SharedArrayBuffer uzatish uchun self.crossOriginIsolated kerak".

6.2 Nega bunday qattiq?

2018-yilda Spectre nomli protsessor zaifligi topildi. Uning mohiyati: juda aniq soat bo'lsa, sahifa protsessor keshining (protsessor ichidagi kichik, juda tez xotiraning) vaqtini o'lchab, bir jarayonda yashaydigan boshqa saytning xotirasini "taxmin qilib o'qishi" mumkin. Umumiy xotira esa juda aniq soat yasashga imkon beradi: bir worker katakni to'xtovsiz oshirib turadi, ikkinchisi uni soat kabi o'qiydi.

Brauzerlar o'shanda SharedArrayBuffer ni butunlay o'chirdi. Keyin shart bilan qaytardi: sahifa o'zini begona saytlardan ajratsa — ya'ni brauzer uni alohida jarayonga joylay olsa, unga umumiy xotira beriladi. Chrome 92 (2021) dan beri bu talab kompyuterda ham majburiy. Bu ajratish — cross-origin isolation (origin'lararo izolyatsiya). Xavfsizlik modelini Brauzer xavfsizlik modeli darsida to'liq ko'ramiz.

6.3 Ikki sarlavha: COOP va COEP

Izolyatsiyani server yoqadi — ikkita HTTP sarlavha bilan (HTTP sarlavhalari):

text
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • COOP (Cross-Origin-Opener-Policy) — "begona sayt oynasi bilan bog'lanmayman". Sahifa boshqa origin'dagi oynani window.open bilan ochsa yoki o'sha oyna uni ochgan bo'lsa ham, ular bir-biriga qo'l uzata olmaydi (window.opener — null).
  • COEP (Cross-Origin-Embedder-Policy) — "begona resursni faqat egasi ruxsat bergan bo'lsa yuklayman". Boshqa origin'dagi rasm, skript, iframe o'zining Cross-Origin-Resource-Policy (CORP) sarlavhasi yoki CORS bilan "meni boshqa saytga qo'yish mumkin" deb aytishi kerak.

Ikkalasi bo'lsa — crossOriginIsolated true bo'ladi. Bittasi kam bo'lsa — yo'q: faqat COEP bilan ham sinadik, false chiqdi.

flowchart TD
  S["Server javobi"] --> C1{"COOP: same-origin?"}
  C1 -- "yo'q" --> X["crossOriginIsolated: false<br>SharedArrayBuffer yo'q"]
  C1 -- "ha" --> C2{"COEP: require-corp<br>yoki credentialless?"}
  C2 -- "yo'q" --> X
  C2 -- "ha" --> OK["crossOriginIsolated: true<br>SharedArrayBuffer bor"]
  OK --> R{"Begona rasm/skript"}
  R -- "CORP yoki CORS bor" --> Y["yuklanadi"]
  R -- "hech biri yo'q" --> B["to'siladi"]

Nimaga qarang: izolyatsiyaning narxi — pastki o'ng tarmoq. Ruxsat bermagan begona resurslar endi yuklanmaydi.

Tekshirib ko'ring: Server faqat Cross-Origin-Embedder-Policy: require-corp yubordi, COOP yo'q. crossOriginIsolated qiymati — .

Javob

false. Izolyatsiya uchun ikkala sarlavha kerak: COOP sahifani begona oynalardan, COEP esa begona resurslardan ajratadi. Bittasi yetishmasa, brauzer sahifani alohida jarayonga joylay olmaydi — SharedArrayBuffer ham berilmaydi.

6.4 Sarlavhali server: npx serve

VS Code'dagi Live Server kengaytmasida o'zingiz HTTP sarlavha qo'shadigan sozlama yo'q. Shuning uchun kichik serve paketini npx bilan ishga tushiramiz. U loyiha papkasidagi serve.json faylidan sarlavhalarni o'qiydi. Papka: kurs/mashqlar/13/20-umumiy-xotira/.

serve.json:

json
{
  "headers": [
    {
      "source": "**/*",
      "headers": [
        {
          "key": "Cross-Origin-Opener-Policy",
          "value": "same-origin"
        },
        {
          "key": "Cross-Origin-Embedder-Policy",
          "value": "require-corp"
        }
      ]
    }
  ]
}

"source": "**/*" — "hamma fayllar uchun". worker.js — oshpaz, "Oshpaz kutadi" bo'limidagi g'oya brauzerda:

js
// worker.js — buyurtmani kutadi
addEventListener("message", (event) => {
  const shared = event.data;
  const result = Atomics.wait(shared, 0, 0); // worker'da — mumkin
  postMessage(`${result}, buyurtma #${Atomics.load(shared, 1)}`);
});

index.html:

html
<!doctype html>
<html lang="uz">
<head>
  <meta charset="utf-8">
  <title>Umumiy xotira</title>
</head>
<body>
  <h1>Oshpaz va kassir</h1>
  <script type="module">
    console.log("crossOriginIsolated:", crossOriginIsolated);
    const shared = new Int32Array(new SharedArrayBuffer(8));
    const worker = new Worker("worker.js");
    worker.addEventListener("message", (event) => {
      console.log("Oshpaz:", event.data);
    });
    worker.postMessage(shared); // nusxa emas — umumiy

    try {
      Atomics.wait(shared, 0, 0, 10);
    } catch (error) {
      console.log(`${error.name}: ${error.message}`);
    }

    setTimeout(() => {
      Atomics.store(shared, 1, 17);
      Atomics.store(shared, 0, 1);
      console.log("Uyg'otildi:", Atomics.notify(shared, 0));
    }, 300);
  </script>
</body>
</html>

Terminalda, shu papkada:

bash
npx serve@14.2.6 .

serve http://localhost:3000 manzilini beradi. Brauzerda oching. Chrome 154 konsolida (biz shunday oldik):

text
crossOriginIsolated: true
TypeError: Atomics.wait cannot be called in this context
Uyg'otildi: 1
Oshpaz: ok, buyurtma #17

serve.json ni o'chirib, serverni qayta ishga tushiring — birinchi qatorda false, keyin Uncaught ReferenceError: SharedArrayBuffer is not defined xatosi chiqadi.

Tekshirishning yana bir yo'li: DevTools → Application → Frames → top. U yerda "Security & Isolation" bo'limida sahifa izolyatsiya qilinganmi va qaysi siyosatlar yoqilgani yozilgan.

6.5 Narxi: begona rasm to'siladi

Sahifaga boshqa origin'dagi rasm qo'ydik (boshqa port — boshqa origin), uning serveri CORP yubormaydi. Chrome 154 konsolida:

text
Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

Uzun nomning ma'nosi: "COEP sabab begona resurs sukut bo'yicha same-origin deb hisoblandi va javob to'sildi". Uch yechim bor:

  1. Resurs serveri Cross-Origin-Resource-Policy: cross-origin yuboradi — sinadik, rasm yuklandi.
  2. Resurs CORS bilan so'raladi: <img crossorigin src="…"> va server Access-Control-Allow-Origin qaytaradi.
  3. COEP'ning yumshoq qiymati: Cross-Origin-Embedder-Policy: credentialless. Begona resurs cookie'siz so'raladi va CORP talab qilinmaydi. Sinadik: crossOriginIsolated: true, o'sha rasm esa yuklandi.

Eng ko'p og'riq — begona iframe'lar (YouTube videosi, xarita, to'lov oynasi) va reklama skriptlari. Ular sarlavhalarni siz emas, o'z egasi boshqaradi. Shuning uchun izolyatsiya odatda butun sayt uchun emas, faqat og'ir hisob kerak bo'lgan sahifa uchun yoqiladi.

Maslahat: Chrome'da yangi yo'l ham bor — bitta sarlavha: Document-Isolation-Policy: isolate-and-require-corp. U COOP'siz ham sahifani izolyatsiya qiladi (Chrome 154 da sinadik: crossOriginIsolated: true). Hozircha faqat Chromium brauzerlarida, shuning uchun COOP + COEP asosiy yo'l bo'lib qoladi.

6.6 Hostingda

  • Netlify — _headers fayli; Vercel, Cloudflare Pages — o'z sozlama fayllari. Sarlavha qo'shish mumkin.
  • GitHub Pages — o'zingiz sarlavha qo'sha olmaysiz. Bunday hostingda Service Worker sahifa javoblariga sarlavhani o'zi qo'shib beradigan usul bor (masalan, coi-serviceworker loyihasi). Service Worker — keyingi darsning mavzusi.

Tekshirib ko'ring: Sahifada COOP va COEP require-corp yoqildi. Sarlavhada begona saytdan logotip rasmi bor edi va u yo'qoldi. Sahifani izolyatsiyadan chiqarmasdan, ikki yechimni ayting.

Javob
  1. Logotip egasidan Cross-Origin-Resource-Policy: cross-origin sarlavhasini qo'yishni so'rash (yoki rasmni CORS bilan berishni). 2) COEP'ni credentialless ga almashtirish — rasm cookie'siz so'raladi va CORP'siz yuklanadi. Uchinchi oddiy yo'l — logotipni o'z serveringizga ko'chirish: o'z origin'ingizdagi fayl hech qachon to'silmaydi.

7. Qachon nima ishlatiladi

Usul Qachon Narxi
postMessage (nusxa) Ko'p holat: buyruq, kichik natija Har xabar nusxalanadi
Transfer (ko'chirish) Katta bufer bir marta (rasm, fayl) Yuboruvchi buferni yo'qotadi
SharedArrayBuffer Doimiy umumiy holat, WebAssembly thread'lari Sinxronlash, COOP/COEP

Oddiy saytda birinchi ikki yo'l deyarli har doim yetarli. SharedArrayBuffer — thread'lar millionlab marta ma'lumot almashadigan joyda: video kodlash, fizik simulyatsiya, C/C++/Rust'dan WebAssembly'ga o'tkazilgan ko'p threadli dasturlar. WebAssembly — brauzerda deyarli mashina tezligida ishlaydigan kod formati; uni kursda chuqur o'rganmaymiz, hozir bilish shart emas.

8. Ko'p uchraydigan xatolar

8.1 SharedArrayBuffer is not defined

Brauzerda sahifa izolyatsiya qilinmagan. Tuzatish: server COOP va COEP yuborsin. Avval console.log(crossOriginIsolated) bilan tekshiring. Live Server buni qila olmaydi — npx serve + serve.json yoki hosting sozlamasi.

8.2 Brauzerning asosiy thread'ida Atomics.wait

TypeError: Atomics.wait cannot be called in this context. Tuzatish: asosiy thread'da Atomics.waitAsync (Promise), wait esa faqat worker ichida.

8.3 Umumiy katakka oddiy ++

Bitta thread'da ishlaydi, ko'p thread'da yangilanishlarni yo'qotadi. Xavfli joyi — kichik sinovda xato ko'rinmasligi mumkin. Tuzatish: bir nechta thread yozadigan katakka faqat Atomics.add, Atomics.compareExchange va boshqalar; yoki har thread'ga alohida katak.

8.4 Bayroqni ma'lumotdan oldin yozish

js
// ❌ oshpaz bayroqni ko'rib uyg'onadi, raqam esa hali yo'q
Atomics.store(shared, 0, 1);
Atomics.notify(shared, 0);
Atomics.store(shared, 1, 17);

Tuzatish: avval ma'lumot, keyin bayroq, keyin notify. Bayroq va ma'lumotni Atomics.store/Atomics.load bilan yozing-o'qing — oddiy shared[1] = 17 da boshqa thread yozuvlarni boshqa tartibda ko'rishi mumkin.

8.5 Float64Array bilan Atomics

TypeError: … is not an integer typed array. Tuzatish: butun sonli turlar (Int32Array, BigInt64Array). Kasr kerak bo'lsa — butun birlikda saqlang (tiyin, millimetr).

8.6 Izolyatsiyadan keyin "rasmlar yo'qoldi"

ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep. Tuzatish: "Narxi: begona rasm to'siladi" bo'limidagi uch yo'l.

9. Mashqlar

1-mashq (oson): Qiymatlarni oldindan ayting

Kodni ishga tushirmasdan, har qatorda nima chiqishini yozing. Keyin tekshiring. Ishora: o'zgartiradigan amal eski qiymatni qaytaradi ("Asosiy amallar" bo'limi).

js
const seats = new Int32Array(new SharedArrayBuffer(4));
Atomics.store(seats, 0, 20); // 20 ta bo'sh o'rin
console.log(Atomics.sub(seats, 0, 4));
console.log(Atomics.compareExchange(seats, 0, 20, 0));
console.log(Atomics.compareExchange(seats, 0, 16, 10));
console.log(Atomics.load(seats, 0));

Birinchi qator , ikkinchisi , uchinchisi , to'rtinchisi chiqaradi.

Yechim
text
20
16
16
10

sub eski 20 ni qaytardi, katakda 16. Birinchi compareExchange 20 ni kutdi, katakda esa 16 — hech narsa yozilmadi, 16 qaytdi. Ikkinchisi 16 ni kutdi — to'g'ri topdi va 10 yozdi; qaytgani yana eski qiymat 16. Oxirida katakda 10.

2-mashq (o'rta): Kassalar savdosi

«Bahor»da uchta kassa bor. Har biri o'z worker'ida 1 000 000 ta chekni sanaydi: har chek 5 000 so'm. Natijalar umumiy Int32Array ga yozilsin, lekin Atomics siz — har worker o'z katagiga (workerData da indeks bering). Asosiy thread kataklarni qo'shib, Jami: … so'm chiqarsin.

Ishora: Int32Array katagiga ko'pi bilan taxminan 2,1 milliard sig'adi. Bitta kassaning savdosi esa 5 milliard so'm — katakda chek sonini saqlang, so'mni oxirida oddiy son sifatida hisoblang.

Yechim
js
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";
import { once } from "node:events";

const REGISTERS = 3;
const RECEIPTS = 1_000_000;
const PRICE = 5000;

if (isMainThread) {
  const counts = new Int32Array(new SharedArrayBuffer(4 * REGISTERS));
  const workers = [];
  for (let index = 0; index < REGISTERS; index++) {
    workers.push(new Worker(new URL(import.meta.url), {
      workerData: { counts, index },
    }));
  }
  await Promise.all(workers.map((w) => once(w, "exit")));
  const total = counts.reduce((sum, n) => sum + n, 0) * PRICE;
  console.log(`Cheklar: ${counts.join(", ")}`);
  console.log(`Jami: ${total} so'm`);
} else {
  const { counts, index } = workerData;
  for (let i = 0; i < RECEIPTS; i++) counts[index]++;
}

Konsolda:

text
Cheklar: 1000000, 1000000, 1000000
Jami: 15000000000 so'm

Har worker faqat o'z katagiga yozadi — to'qnashuv yo'q, shuning uchun oddiy ++ yetarli va Atomics.add dan tezroq. Asosiy thread kataklarni worker'lar tugagandan keyin o'qiydi: once(w, "exit") ularni kutadi. Bu ham sinxronlash — faqat Atomics siz.

3-mashq (qiyin): Qulf (mutex)

Ba'zan bitta emas, ikki katakni birga o'zgartirish kerak: "osh qoldig'i −1" va "sotilgan +1". Ikkalasi orasida boshqa thread aralashmasligi uchun qulf (mutex — mutual exclusion) kerak. Qulf — bitta katak: 0 — ochiq, 1 — band.

lock(arr) va unlock(arr) funksiyalarini yozing (qulf — arr[0]):

  • lock: Atomics.compareExchange(arr, 0, 0, 1) 0 qaytarsa — qulfni oldik. Aks holda Atomics.wait(arr, 0, 1) bilan uxlab, qayta urinamiz.
  • unlock: Atomics.store(arr, 0, 0) va Atomics.notify(arr, 0, 1).

To'rt worker 50 000 martadan "bitta porsiya sotish"ni qulf ichida bajarsin: arr[1] (qoldiq, boshida 200 000) −1, arr[2] (sotilgan) +1 — oddiy -- va ++ bilan. Oxirida qoldiq + sotilgan 200 000 ga teng bo'lishi kerak.

Yechim
js
import {
  Worker, isMainThread, workerData,
} from "node:worker_threads";
import { once } from "node:events";

function lock(arr) {
  while (Atomics.compareExchange(arr, 0, 0, 1) !== 0) {
    Atomics.wait(arr, 0, 1); // band — ochilguncha uxlaymiz
  }
}

function unlock(arr) {
  Atomics.store(arr, 0, 0);
  Atomics.notify(arr, 0, 1); // kutayotganlardan bittasini uyg'ot
}

if (isMainThread) {
  const arr = new Int32Array(new SharedArrayBuffer(12));
  arr[1] = 200_000;
  const workers = [];
  for (let i = 0; i < 4; i++) {
    workers.push(new Worker(new URL(import.meta.url), {
      workerData: arr,
    }));
  }
  await Promise.all(workers.map((w) => once(w, "exit")));
  console.log(`Qoldiq: ${arr[1]}, sotilgan: ${arr[2]}`);
  console.log(`Yig'indi: ${arr[1] + arr[2]}`);
} else {
  const arr = workerData;
  for (let i = 0; i < 50_000; i++) {
    lock(arr);
    arr[1]--; // qulf ichida — faqat bitta thread
    arr[2]++;
    unlock(arr);
  }
}

Konsolda:

text
Qoldiq: 0, sotilgan: 200000
Yig'indi: 200000

compareExchange "agar ochiq bo'lsa — men egallayman" ni bitta bo'linmas qadamda qiladi: ikki thread bir vaqtda qulfni ololmaydi. Band bo'lsa, thread wait bilan uxlaydi va protsessorni band qilmaydi. unlock qulfni ochib, kutayotganlardan bittasini uyg'otadi — u siklning boshiga qaytib, qulfni yana olishga urinadi. Qulfni olib tashlab ko'ring: yig'indi baribir 200 000 chiqishi mumkin, lekin sotilgan 200 000 dan kam bo'ladi — bu yo'qolgan yangilanishlar.

4-mashq: Amaliy tajriba — izolyatsiyani yoqish va o'chirish

kurs/mashqlar/13/20-umumiy-xotira/ papkasida "Sarlavhali server" bo'limidagi uch faylni yarating (index.html, worker.js, serve.json). npx serve@14.2.6 . bilan oching va konsolni tekshiring. Keyin uch tajriba qiling va har birida crossOriginIsolated qiymatini yozib boring:

  1. serve.json dan COOP qatorini o'chiring.
  2. COOP'ni qaytaring, COEP qiymatini credentialless ga almashtiring.
  3. COEP'ni require-corp ga qaytaring. Yonida rasmlar/ papkasi oching, unga istalgan osh.png qo'ying va ikkinchi terminalda npx serve@14.2.6 -l 3001 . bilan ishga tushiring — bu boshqa port, demak boshqa origin. Sahifaga <img src="http://localhost:3001/osh.png" alt="Osh"> qo'shing.

vazifalar ga bu darsda qadam yo'q — unga umumiy xotira kerak emas.

Yechim
  1. false — COEP yolg'iz yetmaydi. SharedArrayBuffer is not defined xatosi chiqadi.
  2. true — credentialless ham izolyatsiya beradi.
  3. true, lekin rasm chiqmaydi: Network panelida so'rov qizil, konsolda ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep. Rasm serverini npx serve@14.2.6 -l 3001 --cors . bilan qayta ishga tushiring (--cors — CORS sarlavhasini qo'shadi) va tegga crossorigin qo'shing: <img crossorigin src="…">. Rasm CORS bilan so'raladi va yuklanadi. Yoki 2-tajribadagidek credentialless — unda rasm crossorigin siz ham chiqadi. Uchala holatni Chrome 154 da sinadik.

serve.json o'zgargandan keyin serve ni qayta ishga tushiring (Ctrl + C, keyin yana npx serve@14.2.6 .) va sahifani Ctrl + Shift + R bilan yangilang.

bash
git add 13/20-umumiy-xotira
git commit -m "13/20: SharedArrayBuffer va COOP/COEP tajribasi"

10. Real ishda

  • Video va audio brauzerda. ffmpeg.wasm'ning ko'p threadli versiyasi, Photoshop'ning veb versiyasi, Figma — SharedArrayBuffer + WebAssembly thread'lari. Ularning hujjatida birinchi talab: "COOP va COEP sarlavhalarini qo'ying".
  • O'yin dvigatellari. Unity va Godot'ning veb eksporti ko'p threadli rejimda shu sarlavhalarni talab qiladi.
  • Node.js serverlar. worker_threads havzalari (masalan, Piscina kutubxonasi) — og'ir hisob uchun; umumiy hisoblagich va navbatlar Atomics bilan. Node server — 24-qismda.
  • Xavfsizlik. COOP, COEP va CORP — zamonaviy saytlarning xavfsizlik sarlavhalari ro'yxatida. Ular faqat SharedArrayBuffer uchun emas, sahifani begona oynalardan himoya qilish uchun ham qo'yiladi.
  • Intervyu. "Nega SharedArrayBuffer ishlamayapti?", "race condition nima va qanday tuzatiladi?", "Atomics.wait ni nega asosiy thread'da chaqirib bo'lmaydi?" — senior frontend va Node intervyularida uchraydi.

Xulosa

  • postMessage — nusxa, transfer — egasi almashadi, SharedArrayBuffer — bitta xotira, bir nechta thread.
  • Umumiy katakka oddiy ++ yangilanishlarni yo'qotadi; Atomics amallari bo'linmas va eski qiymatni qaytaradi.
  • Atomics.wait — worker'ni uxlatadi (asosiy thread'da taqiqlangan), notify — uyg'otadi; asosiy thread'da — Atomics.waitAsync.
  • Avval ma'lumot, keyin bayroq, keyin notify; wait ning qiymat tekshiruvi kech kelgan thread'ni abadiy uxlashdan saqlaydi.
  • Brauzerda SharedArrayBuffer faqat crossOriginIsolated sahifada: COOP same-origin + COEP require-corp (yoki credentialless).
  • Izolyatsiyaning narxi — ruxsat bermagan begona resurslar to'siladi.

Keyingi dars: Service Worker hayot sikli — sahifa bilan tarmoq orasida turadigan maxsus worker: u qanday o'rnatiladi, faollashadi va yangilanadi.

Manbalar

  • MDN: "SharedArrayBuffer", "Atomics", "Cross-Origin-Opener-Policy", "Cross-Origin-Embedder-Policy" — developer.mozilla.org
  • web.dev: "Making your website cross-origin isolated using COOP and COEP" — web.dev/articles/coop-coep
  • Node.js 24 hujjatlari: "Worker threads" — nodejs.org/api/worker_threads.html
  • ECMAScript 2026: Atomics.pause — tc39.es/ecma262
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript SharedArrayBuffer va Atomics: worker'lar orasida umumiy xotira — IlmHamroh