Mundarija (39)
- Bu darsda
- 1. Nega bu kerak?
- 2. Umumiy xotira: SharedArrayBuffer
- 2.1 Node'da sinab ko'ramiz
- 2.2 Uch yo'l — bitta rasmda
- 3. Race condition: yangilanish yo'qoladi
- 3.1 To'rt oshpaz, bitta hisoblagich
- 3.2 Nega yo'qoladi
- 4. Atomics: bo'linmas amallar
- 4.1 Tuzatish
- 4.2 Asosiy amallar
- 5. Kutish va uyg'otish: wait va notify
- 5.1 Oshpaz buyurtmani kutadi
- 5.2 wait qaytaradigan uch qiymat
- 5.3 Asosiy thread'da — Atomics.waitAsync
- 5.4 Qisqa aylanish: Atomics.pause
- 6. Brauzerda: cross-origin isolation
- 6.1 Natija oynasida SharedArrayBuffer yo'q
- 6.2 Nega bunday qattiq?
- 6.3 Ikki sarlavha: COOP va COEP
- 6.4 Sarlavhali server: npx serve
- 6.5 Narxi: begona rasm to'siladi
- 6.6 Hostingda
- 7. Qachon nima ishlatiladi
- 8. Ko'p uchraydigan xatolar
- 8.1 SharedArrayBuffer is not defined
- 8.2 Brauzerning asosiy thread'ida Atomics.wait
- 8.3 Umumiy katakka oddiy ++
- 8.4 Bayroqni ma'lumotdan oldin yozish
- 8.5 Float64Array bilan Atomics
- 8.6 Izolyatsiyadan keyin "rasmlar yo'qoldi"
- 9. Mashqlar
- 1-mashq (oson): Qiymatlarni oldindan ayting
- 2-mashq (o'rta): Kassalar savdosi
- 3-mashq (qiyin): Qulf (mutex)
- 4-mashq: Amaliy tajriba — izolyatsiyani yoqish va o'chirish
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript SharedArrayBuffer va Atomics: worker'lar orasida umumiy xotira
Qisqacha: Oddiy
postMessagema'lumotni worker'ga nusxalaydi.SharedArrayBufferesa bitta xotirani bir nechta thread'ga umumiy qiladi: biri yozsa, boshqasi darhol ko'radi. Umumiy xotiradacounter[0]++kabi oddiy amal yangilanishlarni yo'qotadi (race condition) — shuning uchunAtomics.add,Atomics.waitvaAtomics.notifyishlatiladi. BrauzerdaSharedArrayBufferfaqat cross-origin isolation yoqilgan sahifada bor: serverCross-Origin-Opener-PolicyvaCross-Origin-Embedder-Policysarlavhalarini yuborishi kerak.
Bu darsda
- Nusxa, ko'chirish (transfer) va umumiy xotira farqini tushuntira olasiz.
- Umumiy hisoblagichda yangilanish qanday yo'qolishini ko'rasiz va uni
Atomicsbilan tuzatasiz. - Bir thread'ni
Atomics.waitbilan uxlatib, boshqasidanAtomics.notifybilan uyg'ota olasiz; asosiy thread'da esaAtomics.waitAsyncishlatasiz. - 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—truebo'lsa, kod asosiy thread'da,falsebo'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:
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:
ArrayBuffer: 0
SharedArrayBuffer: 42Oddiy 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
endSharedArrayBuffer 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
SharedArrayBufferyuborildi, keyin asosiy thread unga7yozdi. Worker o'sha katakni o'qisa, nimani ko'radi? OddiyArrayBufferbo'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:
// 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):
Natija: 1078968, kutilgan: 4000000
Natija: 1989251, kutilgan: 4000000
Natija: 1954750, kutilgan: 4000000
Natija: 2071922, kutilgan: 4000000
Natija: 2569765, kutilgan: 4000000Har 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:
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:
Natija: 4000000, kutilgan: 4000000Har 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:
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. Oddiystock[0] = 10dan 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 halikutilganturgan bo'lsa —yangini yoz". Birinchi chaqiruvda katak0edi —7yozildi. Ikkinchisida katak endi7,0emas — hech narsa yozilmadi,7qaytdi. Qulf (lock) shu amal bilan yasaladi — 3-mashqda.
Atomics faqat butun sonli typed array bilan ishlaydi: Int8Array … BigInt64Array. Float64Array bersangiz:
const prices = new Float64Array(new SharedArrayBuffer(8));
Atomics.add(prices, 0, 1);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 haliqiymatbo'lsa — uxla". Thread to'xtaydi.Atomics.notify(arr, i, soni)— shu katakni kutayotganlarni uyg'otadi (soniberilmasa — hammasini). Nechta thread uyg'onganini qaytaradi.
Oshpaz (worker) buyurtmani kutadi. Kassir (asosiy thread) 300 ms dan keyin buyurtma raqamini yozib, uni uyg'otadi:
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:
Kassir: buyurtma yozildi, uyg'otildi: 1
Oshpaz: ok, buyurtma #17Qatorma-qator:
- Worker
Atomics.wait(shared, 0, 0)da to'xtadi: katak0— 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. notifybitta uxlayotganni uyg'otdi —1qaytardi.waitesa"ok"qaytardi.parentPort.postMessage— Node worker'idan asosiy thread'ga xabar; brauzerdagipostMessagening 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:
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"— katakda7emas,0turibdi. Uxlashning hojati yo'q: "kutgan narsam allaqachon o'zgargan"."timed-out"— katak0, thread uxladi, 50 ms ichida hech kim uyg'otmadi."ok"— uxladi vanotifybilan 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
waitga yetib kelmasidan oldin kassirnotifyqildi.notifynechani qaytaradi va oshpazningwaiti 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:
TypeError: Atomics.wait cannot be called in this contextTarjimasi: "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:
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:
async: true
Kassir kutmoqda, lekin bloklanmagan
Natija: ok
Tayyor porsiyalar: 3waitAsync 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:
// 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:
<p>Konsolga qarang.</p>
<script>
console.log("crossOriginIsolated:", crossOriginIsolated);
console.log("SharedArrayBuffer:", typeof SharedArrayBuffer);
</script>Konsolda:
crossOriginIsolated: false
SharedArrayBuffer: undefinedKonstruktor 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:
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):
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.openbilan 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-corpyubordi, COOP yo'q.crossOriginIsolatedqiymati —.
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:
{
"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:
// 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:
<!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:
npx serve@14.2.6 .serve http://localhost:3000 manzilini beradi. Brauzerda oching. Chrome 154 konsolida (biz shunday oldik):
crossOriginIsolated: true
TypeError: Atomics.wait cannot be called in this context
Uyg'otildi: 1
Oshpaz: ok, buyurtma #17serve.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:
Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoepUzun nomning ma'nosi: "COEP sabab begona resurs sukut bo'yicha same-origin deb hisoblandi va javob to'sildi". Uch yechim bor:
- Resurs serveri
Cross-Origin-Resource-Policy: cross-originyuboradi — sinadik, rasm yuklandi. - Resurs CORS bilan so'raladi:
<img crossorigin src="…">va serverAccess-Control-Allow-Originqaytaradi. - 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 —
_headersfayli; 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-serviceworkerloyihasi). Service Worker — keyingi darsning mavzusi.
Tekshirib ko'ring: Sahifada COOP va COEP
require-corpyoqildi. Sarlavhada begona saytdan logotip rasmi bor edi va u yo'qoldi. Sahifani izolyatsiyadan chiqarmasdan, ikki yechimni ayting.
Javob
- Logotip egasidan
Cross-Origin-Resource-Policy: cross-originsarlavhasini qo'yishni so'rash (yoki rasmni CORS bilan berishni). 2) COEP'nicredentiallessga 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
// ❌ 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).
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
20
16
16
10sub 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
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:
Cheklar: 1000000, 1000000, 1000000
Jami: 15000000000 so'mHar 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)0qaytarsa — qulfni oldik. Aks holdaAtomics.wait(arr, 0, 1)bilan uxlab, qayta urinamiz.unlock:Atomics.store(arr, 0, 0)vaAtomics.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
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:
Qoldiq: 0, sotilgan: 200000
Yig'indi: 200000compareExchange "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:
serve.jsondan COOP qatorini o'chiring.- COOP'ni qaytaring, COEP qiymatini
credentiallessga almashtiring. - COEP'ni
require-corpga qaytaring. Yonidarasmlar/papkasi oching, unga istalganosh.pngqo'ying va ikkinchi terminaldanpx 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
false— COEP yolg'iz yetmaydi.SharedArrayBuffer is not definedxatosi chiqadi.true—credentiallessham izolyatsiya beradi.true, lekin rasm chiqmaydi: Network panelida so'rov qizil, konsoldaERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep. Rasm serverininpx serve@14.2.6 -l 3001 --cors .bilan qayta ishga tushiring (--cors— CORS sarlavhasini qo'shadi) va teggacrossoriginqo'shing:<img crossorigin src="…">. Rasm CORS bilan so'raladi va yuklanadi. Yoki 2-tajribadagidekcredentialless— unda rasmcrossoriginsiz 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.
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_threadshavzalari (masalan, Piscina kutubxonasi) — og'ir hisob uchun; umumiy hisoblagich va navbatlarAtomicsbilan. Node server — 24-qismda. - Xavfsizlik. COOP, COEP va CORP — zamonaviy saytlarning xavfsizlik sarlavhalari ro'yxatida. Ular faqat
SharedArrayBufferuchun emas, sahifani begona oynalardan himoya qilish uchun ham qo'yiladi. - Intervyu. "Nega
SharedArrayBufferishlamayapti?", "race condition nima va qanday tuzatiladi?", "Atomics.waitni 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;Atomicsamallari 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;waitning qiymat tekshiruvi kech kelgan thread'ni abadiy uxlashdan saqlaydi. - Brauzerda
SharedArrayBufferfaqatcrossOriginIsolatedsahifada: COOPsame-origin+ COEPrequire-corp(yokicredentialless). - 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!