Ana iş parçacığı dışı bir mimari, uygulamanızın güvenilirliğini ve kullanıcı deneyimini önemli ölçüde iyileştirebilir.
Son 20 yılda web, birkaç stil ve resim içeren statik dokümanlardan karmaşık ve dinamik uygulamalara doğru önemli bir evrim geçirdi. Ancak bir şey büyük ölçüde değişmeden kaldı: Sitelerimizi oluşturma ve JavaScript'imizi çalıştırma işini yapmak için tarayıcı sekmesi başına yalnızca bir iş parçacığımız var (bazı istisnalar hariç).
Bu nedenle, ana iş parçacığı aşırı yüklendi. Web uygulamalarının karmaşıklığı arttıkça ana iş parçacığı, performans açısından önemli bir darboğaz haline gelir. Daha da kötüsü, cihaz özelliklerinin performans üzerinde büyük etkisi olduğundan, belirli bir kullanıcı için ana iş parçacığında kod çalıştırmak neredeyse tamamen tahmin edilemez. Kullanıcılar, web'e erişmek için giderek daha çeşitli cihazlar kullanmaya başladıkça bu öngörülemezlik artacaktır. Bu cihazlar arasında, çok kısıtlı özellikli telefonlardan yüksek performanslı ve yüksek yenileme hızlı amiral gemisi cihazlara kadar pek çok seçenek yer alır.
İnsan algısı ve psikolojisiyle ilgili deneysel verilere dayanan Core Web Vitals gibi performans yönergelerini karmaşık web uygulamalarının güvenilir bir şekilde karşılamasını istiyorsak kodumuzu ana iş parçacığı dışında (OMT) yürütmenin yollarını bulmamız gerekir.
Neden web worker'ları?
JavaScript, varsayılan olarak görevleri ana iş parçacığında çalıştıran tek iş parçacıklı bir dildir. Ancak web çalışanları, geliştiricilerin ana iş parçacığı dışındaki işleri yönetmek için ayrı iş parçacıkları oluşturmasına olanak tanıyarak ana iş parçacığından bir tür kaçış yolu sağlar. Web çalışanlarının kapsamı sınırlı olsa ve DOM'a doğrudan erişim sunmasa da ana iş parçacığını aksi takdirde aşırı yükleyecek önemli bir çalışma varsa çok faydalı olabilirler.
Core Web Vitals söz konusu olduğunda, işi ana iş parçacığı dışında çalıştırmak faydalı olabilir. Özellikle, ana iş parçacığındaki işi web çalışanlarına aktarmak ana iş parçacığı için çekişmeyi azaltabilir. Bu da bir sayfanın Interaction to Next Paint (INP) duyarlılık metriğini iyileştirebilir. Ana ileti dizisinin işleyeceği iş yükü azaldığında kullanıcı etkileşimlerine daha hızlı yanıt verebilir.
Daha az ana ileti dizisi çalışması (özellikle başlangıç sırasında), uzun görevleri azaltarak Largest Contentful Paint (LCP) için de potansiyel bir avantaj sağlar. LCP öğesinin oluşturulması için ana iş parçacığı süresi gerekir. Bu süre, sık ve yaygın LCP öğeleri olan metin veya resimlerin oluşturulması için kullanılır. Ana iş parçacığı çalışmasını genel olarak azaltarak sayfanızın LCP öğesinin, bunun yerine bir web çalışanı tarafından işlenebilecek maliyetli çalışma tarafından engellenme olasılığını azaltabilirsiniz.
Web çalışanlarıyla iş parçacığı oluşturma
Diğer platformlar genellikle bir iş parçacığına, programınızın geri kalanıyla paralel olarak çalışan bir işlev atamanıza izin vererek paralel çalışmayı destekler. Aynı değişkenlere her iki iş parçacığından da erişebilirsiniz. Bu paylaşılan kaynaklara erişim, yarışma koşullarını önlemek için karşılıklı dışlama (mutex) ve semaforlarla senkronize edilebilir.
JavaScript'te, 2007'den beri kullanılan ve 2012'den beri tüm büyük tarayıcılarda desteklenen web çalışanları ile benzer işlevler elde edebiliriz. Web çalışanları ana iş parçacığıyla paralel olarak çalışır ancak işletim sistemi iş parçacığının aksine değişkenleri paylaşamaz.
Web çalışanı oluşturmak için, dosyayı ayrı bir iş parçacığında çalıştırmaya başlayan çalışan oluşturucusuna bir dosya iletin:
const worker = new Worker("./worker.js");
postMessage API'yi kullanarak mesaj göndererek web çalışanıyla iletişim kurun. Mesaj değerini postMessage çağrısında parametre olarak iletin ve ardından çalışana bir mesaj etkinlik dinleyicisi ekleyin:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
Ana iş parçacığına mesaj göndermek için web worker'da aynı postMessage API'yi kullanın ve ana iş parçacığında bir etkinlik dinleyici ayarlayın:
main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a + b);
});
Bu yaklaşımın biraz sınırlı olduğu kabul edilmelidir. Geçmişte web çalışanları, genellikle tek bir ağır işi ana iş parçacığının dışına taşımak için kullanılıyordu. Tek bir web çalışanıyla birden fazla işlemi yönetmeye çalışmak kısa sürede zorlaşır: Yalnızca parametreleri değil, işlemi de mesajda kodlamanız ve yanıtları isteklere eşleştirmek için muhasebe yapmanız gerekir. Bu karmaşıklık, web çalışanlarının daha yaygın olarak kullanılmamasının muhtemel nedenidir.
Ancak ana iş parçacığı ile web işçileri arasındaki iletişimin zorluğunu azaltabilirsek bu model, birçok kullanım alanı için mükemmel bir seçenek olabilir. Neyse ki bu işi yapan bir kitaplık var.
Comlink: Web çalışanlarını daha az yorucu hale getirme
Comlink, postMessage ayrıntılarını düşünmek zorunda kalmadan web çalışanlarını kullanmanızı sağlayan bir kitaplıktır. Comlink, iş parçacığı oluşturmayı destekleyen diğer programlama dillerine benzer şekilde, web işçileri ve ana iş parçacığı arasında değişken paylaşmanıza olanak tanır.
Comlink'i bir web worker'a aktarıp ana iş parçacığına sunulacak bir işlev grubu tanımlayarak ayarlarsınız. Ardından, ana iş parçacığına Comlink'i aktarır, çalışanı sarmalar ve kullanıma sunulan işlevlere erişirsiniz:
worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
Ana iş parçacığındaki api değişkeni, her işlevin değerin kendisi yerine değer için bir söz döndürmesi dışında web worker'daki değişkenle aynı şekilde davranır.
Hangi kodu web worker'a taşımanız gerekir?
Web worker'lar DOM'a ve WebUSB, WebRTC veya Web Audio gibi birçok API'ye erişemez. Bu nedenle, uygulamanızın bu tür erişime dayanan kısımlarını bir worker'a yerleştiremezsiniz. Yine de, her küçük kod parçası bir çalışana taşındığında ana iş parçacığında, kullanıcı arayüzünü güncelleme gibi olması gereken şeyler için daha fazla yer açılır.
Web geliştiricilerin karşılaştığı sorunlardan biri, çoğu web uygulamasının uygulamadaki her şeyi düzenlemek için Vue veya React gibi bir kullanıcı arayüzü çerçevesine dayanmasıdır. Her şey, çerçevenin bir bileşenidir ve bu nedenle DOM'a bağlıdır. Bu durum, OMT mimarisine geçişi zorlaştırıyor gibi görünüyor.
Ancak kullanıcı arayüzüyle ilgili sorunların durum yönetimi gibi diğer sorunlardan ayrıldığı bir modele geçiş yaparsak web worker'lar, çerçeve tabanlı uygulamalarda bile oldukça faydalı olabilir. PROXX'ta tam olarak bu yaklaşım benimsenmiştir.
PROXX: Bir OMT örnek olayı
Google Chrome Ekibi, PROXX'u, çevrimdışı çalışma ve ilgi çekici bir kullanıcı deneyimi sunma gibi progresif web uygulaması gereksinimlerini karşılayan bir Mayın Tarlası klonu olarak geliştirdi. Maalesef oyunun ilk sürümleri, özellikli telefonlar gibi sınırlı cihazlarda iyi performans göstermedi. Bu durum, ekibin ana iş parçacığının bir darboğaz olduğunu fark etmesine neden oldu.
Ekip, oyunun görsel durumunu mantığından ayırmak için web worker'ları kullanmaya karar verdi:
- Ana iş parçacığı, animasyonların ve geçişlerin oluşturulmasını sağlar.
- Web çalışanı, tamamen hesaplamaya dayalı olan oyun mantığını işler.
OMT, PROXX'un özellikli telefon performansını ilginç bir şekilde etkiledi. OMT dışı sürümde, kullanıcı etkileşimde bulunduktan sonra kullanıcı arayüzü altı saniye boyunca dondurulur. Geri bildirim yoktur ve kullanıcının başka bir işlem yapabilmesi için altı saniyenin tamamlanmasını beklemesi gerekir.
Ancak OMT sürümünde, oyunun kullanıcı arayüzü güncellemesini tamamlaması on iki saniye sürüyor. Bu durum performans kaybı gibi görünse de aslında kullanıcıya daha fazla geri bildirim verilmesini sağlar. Yavaşlamanın nedeni, uygulamanın hiç çerçeve göndermeyen OMT dışı sürümden daha fazla çerçeve göndermesidir. Bu nedenle kullanıcı, bir şeylerin olduğunu bilir ve kullanıcı arayüzü güncellenirken oyuna devam edebilir. Bu da oyunun çok daha iyi bir deneyim sunmasını sağlar.
Bu bilinçli bir değiş tokuştur: Kısıtlı cihazların kullanıcılarına üst düzey cihazların kullanıcılarını cezalandırmadan daha iyi bir deneyim sunarız.
OMT mimarisinin etkileri
PROXX örneğinde gösterildiği gibi, OMT uygulamanızın daha geniş bir cihaz aralığında güvenilir bir şekilde çalışmasını sağlar ancak uygulamanızı daha hızlı hale getirmez:
- Yalnızca işi ana iş parçacığından taşıyorsunuz, işi azaltmıyorsunuz.
- Web çalışanı ile ana iş parçacığı arasındaki ek iletişim yükü, bazen işlemleri biraz daha yavaşlatabilir.
Artıları ve eksileri değerlendirme
Ana iş parçacığı, JavaScript çalışırken kaydırma gibi kullanıcı etkileşimlerini işlemek için serbest olduğundan, toplam bekleme süresi biraz daha uzun olsa da bırakılan kare sayısı daha azdır. Kullanıcıyı biraz bekletmek, kare düşürmekten daha iyidir. Çünkü kare düşürme durumunda hata payı daha küçüktür. Kare düşürme milisaniyeler içinde gerçekleşirken kullanıcı bekleme süresini algılamadan önce yüzlerce milisaniye vardır.
Cihazlar arası performansın tahmin edilemezliği nedeniyle OMT mimarisinin amacı, paralelleştirmenin performans avantajlarından ziyade riski azaltmaktır. Bu sayede, uygulamanız çalışma zamanı koşullarının değişkenliğine karşı daha dayanıklı hale gelir. Dayanıklılığın artması ve kullanıcı deneyimindeki iyileştirmeler, hızdaki küçük bir dezavantajdan çok daha değerlidir.
Araçlar hakkında not
Web çalışanları henüz yaygınlaşmadığı için webpack ve Rollup gibi çoğu modül aracı bunları kutudan çıktığı haliyle desteklemez. (Parcel'da ise bu özellik mevcuttur.) Neyse ki web worker'ların webpack ve Rollup ile çalışmasını sağlayan eklentiler var:
- webpack için worker-plugin
- Rollup için rollup-plugin-off-main-thread
Özetleme
Uygulamalarımızın özellikle giderek küreselleşen bir pazarda mümkün olduğunca güvenilir ve erişilebilir olmasını sağlamak için sınırlı cihazları desteklememiz gerekiyor. Çünkü dünya genelinde çoğu kullanıcı web'e bu cihazlarla erişiyor. OMT, bu tür cihazlarda performansı artırmak için umut verici bir yöntem sunar ve üst düzey cihaz kullanıcılarını olumsuz etkilemez.
Ayrıca, OMT'nin ikincil avantajları da vardır:
- JavaScript yürütme maliyetlerini ayrı bir iş parçacığına taşır.
- Ayrıştırma maliyetlerini ortadan kaldırır. Bu sayede kullanıcı arayüzü daha hızlı başlatılabilir. Bu, İlk Zengin İçerikli Boyama veya hatta Etkileşime Hazır Olma Süresi'ni azaltabilir. Bu da Lighthouse puanınızı artırabilir.
Web çalışanları korkutucu olmak zorunda değildir. Comlink gibi araçlar, çalışanların işini kolaylaştırarak onları çok çeşitli web uygulamaları için uygun bir seçenek haline getiriyor.