JavaScript'te çoklu iş parçacığı yapmanın bir yolu var mı?
JavaScript'te çoklu iş parçacığı yapmanın bir yolu var mı?
Yanıtlar:
En güncel destek bilgileri için http://caniuse.com/#search=worker adresine bakın .
Aşağıdakiler 2009 yılındaki destek durumuydu.
Google için istediğiniz kelimeler JavaScript Çalışan Konularıdır
Gears'ın dışında şu anda mevcut hiçbir şey yok, ancak bunu nasıl uygulayacağınız hakkında bolca konuşma var, bu yüzden sanırım cevap gelecekte değişecek çünkü bu soruyu izleyin.
Gears için ilgili belgeler: WorkerPool API
WHATWG'nin çalışan iş parçacıkları için bir Taslak Tavsiyesi vardır: Web Çalışanları
Ayrıca Mozilla'nın DOM İşçi Konuları da var
Güncelleme: Haziran 2009, JavaScript dizileri için tarayıcı desteğinin geçerli durumu
Firefox 3.5 web çalışanlarına sahiptir. Onları hareket halinde görmek istiyorsanız, web çalışanlarından bazı demolar:
Gears eklentisi Firefox'a da yüklenebilir.
Safari 4 ve WebKit gecelerinde çalışan iş parçacıkları vardır:
Chrome'da Gears'ın pişirilmiş olması nedeniyle, kullanıcıdan onaylama istemi gerektirmesine rağmen iş parçacıkları yapabilir (ve Gears eklentisi yüklü herhangi bir tarayıcıda çalışmasına rağmen web çalışanları için farklı bir API kullanır):
IE8 ve IE9 yalnızca Gears eklentisi yüklenmiş konuları yapabilir
HTML5'ten önce JavaScript, sayfa başına yalnızca bir iş parçacığının yürütülmesine izin verdi.
Asenkron yürütme taklit etmek için bir hacky şekilde oldu Verim , setTimeout()
, setInterval()
, XMLHttpRequest
veya olay işleyicileri ile (bir örnek için bu yazının sonuna bakınız verim vesetTimeout()
).
Ancak HTML5 ile artık çalışanların iş parçacıklarını işlevlerin yürütülmesini paralelleştirmek için kullanabiliriz. İşte bir kullanım örneği.
HTML5 Web Çalışan Konularını tanıttı (bkz: tarayıcı uyumlulukları )
Not: IE9 ve önceki sürümleri bunu desteklemez.
Bu çalışan iş parçacıkları, sayfanın performansını etkilemeden arka planda çalışan JavaScript iş parçacıklarıdır. Web Çalışanı hakkında daha fazla bilgi için belgeleri veya bu eğitimi okuyun .
MAX_VALUE değerine sayılan ve sayfamızdaki geçerli hesaplanan değeri gösteren 3 Web Çalışanı iş parçacığına ilişkin basit bir örnek:
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
var MAX_VALUE = 10000;
/*
* Here are the workers
*/
//Worker 1
var worker1 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
document.getElementById("result1").innerHTML = e.data;
}, false);
//Worker 2
var worker2 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker2.addEventListener('message', function(e) {
document.getElementById("result2").innerHTML = e.data;
}, false);
//Worker 3
var worker3 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker3.addEventListener('message', function(e) {
document.getElementById("result3").innerHTML = e.data;
}, false);
// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Üç iş parçacığının eşzamanlı olarak yürütüldüğünü görebilir ve geçerli değerlerini sayfada yazdırabiliriz. Ayrılmış iş parçacıkları ile arka planda yürütüldükleri için sayfayı dondurmazlar.
Bunu başarmanın başka bir yolu, birden fazla iframe kullanmaktır , her biri bir iş parçacığı yürütür. İframe'e URL ile bazı parametreler verebiliriz ve iframe sonucu almak ve geri yazdırmak için üst öğesiyle iletişim kurabilir ( iframe aynı etki alanında olmalıdır).
Bu örnek tüm tarayıcılarda çalışmaz! iframe'ler genellikle ana sayfa ile aynı iş parçacığında / işlemde çalışır (ancak Firefox ve Chromium farklı şekilde işliyor gibi görünüyor).
Kod pasajı birden fazla HTML dosyasını desteklemediğinden, burada sadece farklı kodlar sunacağım:
index.html:
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>
//Divs that shows the result
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
<script>
//This function is called by each iframe
function threadResult(threadId, result) {
document.getElementById("result" + threadId).innerHTML = result;
}
</script>
thread.html:
//Get the parameters in the URL: http://stackoverflow.com/a/1099670/2576706
function getQueryParams(paramName) {
var qs = document.location.search.split('+').join(' ');
var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params[paramName];
}
//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
var threadId = getQueryParams('id');
for(var i=0; i<MAX_VALUE; i++){
parent.threadResult(threadId, i);
}
})();
'Saf' yol, işlevi setTimeout()
birbiri ardına şu şekilde yürütmek olacaktır :
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Ancak bu yöntem çalışmaz, çünkü her görev birbiri ardına yürütülecektir.
İşlevi şu şekilde bu şekilde çağırarak eşzamansız yürütmeyi simüle edebiliriz:
var MAX_VALUE = 10000;
function thread1(value, maxValue){
var me = this;
document.getElementById("result1").innerHTML = value;
value++;
//Continue execution
if(value<=maxValue)
setTimeout(function () { me.thread1(value, maxValue); }, 0);
}
function thread2(value, maxValue){
var me = this;
document.getElementById("result2").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread2(value, maxValue); }, 0);
}
function thread3(value, maxValue){
var me = this;
document.getElementById("result3").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread3(value, maxValue); }, 0);
}
thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Gördüğünüz gibi, bu ikinci yöntem çok yavaş ve işlevleri yürütmek için ana iş parçacığı kullandığından tarayıcıyı donduruyor.
Verim , ECMAScript 6'daki yeni bir özelliktir, yalnızca Firefox ve Chrome'un en eski sürümünde çalışır (Chrome'da chrome: // flags / # enable-javascript-harmony'de görünen Deneysel JavaScript'i etkinleştirmeniz gerekir).
Verim anahtar sözcüğü, jeneratör işlevi yürütmesinin duraklamasına neden olur ve verim anahtar sözcüğünü izleyen ifadenin değeri, üretecin arayanına döndürülür. Dönüş anahtar kelimesinin jeneratör tabanlı bir sürümü olarak düşünülebilir.
Bir jeneratör, bir fonksiyonun yürütülmesini askıya almanıza ve daha sonra devam etmenize izin verir. Bir jeneratör, trambolin adı verilen bir teknikle işlevlerinizi programlamak için kullanılabilir .
İşte örnek:
var MAX_VALUE = 10000;
Scheduler = {
_tasks: [],
add: function(func){
this._tasks.push(func);
},
start: function(){
var tasks = this._tasks;
var length = tasks.length;
while(length>0){
for(var i=0; i<length; i++){
var res = tasks[i].next();
if(res.done){
tasks.splice(i, 1);
length--;
i--;
}
}
}
}
}
function* updateUI(threadID, maxValue) {
var value = 0;
while(value<=maxValue){
yield document.getElementById("result" + threadID).innerHTML = value;
value++;
}
}
Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));
Scheduler.start()
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Javascript'te çoklu iş parçacığını simüle etmenin bir yolu
Şimdi sayılar eklenmesini hesaplayacak 3 iş parçacığı oluşturacağım, sayılar 13'e bölünebilir ve sayılar 3'e 10000000000'e kadar bölünebilir. Ama size bu işlevlerin aynı zamanda yinelemeli çalışmasını sağlayacak bir numara göstereceğim: jsFiddle
Bu kod bana ait.
Vücut parçası
<div class="div1">
<input type="button" value="start/stop" onclick="_thread1.control ? _thread1.stop() : _thread1.start();" /><span>Counting summation of numbers till 10000000000</span> = <span id="1">0</span>
</div>
<div class="div2">
<input type="button" value="start/stop" onclick="_thread2.control ? _thread2.stop() : _thread2.start();" /><span>Counting numbers can be divided with 13 till 10000000000</span> = <span id="2">0</span>
</div>
<div class="div3">
<input type="button" value="start/stop" onclick="_thread3.control ? _thread3.stop() : _thread3.start();" /><span>Counting numbers can be divided with 3 till 10000000000</span> = <span id="3">0</span>
</div>
Javascript Bölümü
var _thread1 = {//This is my thread as object
control: false,//this is my control that will be used for start stop
value: 0, //stores my result
current: 0, //stores current number
func: function () { //this is my func that will run
if (this.control) { // checking for control to run
if (this.current < 10000000000) {
this.value += this.current;
document.getElementById("1").innerHTML = this.value;
this.current++;
}
}
setTimeout(function () { // And here is the trick! setTimeout is a king that will help us simulate threading in javascript
_thread1.func(); //You cannot use this.func() just try to call with your object name
}, 0);
},
start: function () {
this.control = true; //start function
},
stop: function () {
this.control = false; //stop function
},
init: function () {
setTimeout(function () {
_thread1.func(); // the first call of our thread
}, 0)
}
};
var _thread2 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 13 == 0) {
this.value++;
}
this.current++;
document.getElementById("2").innerHTML = this.value;
}
setTimeout(function () {
_thread2.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread2.func();
}, 0)
}
};
var _thread3 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 3 == 0) {
this.value++;
}
this.current++;
document.getElementById("3").innerHTML = this.value;
}
setTimeout(function () {
_thread3.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread3.func();
}, 0)
}
};
_thread1.init();
_thread2.init();
_thread3.init();
Umarım bu şekilde yardımcı olur.
Kodunuzu bir durum makinesine dönüştürecek ve etkili bir şekilde iş parçacığını taklit etmenizi sağlayacak bir derleyici olan Anlatım JavaScript'i kullanabilirsiniz . Bunu, tek bir doğrusal kod bloğunda eşzamansız kod yazmanıza izin veren dile "verim" operatörü ('->' olarak işaretlenmiş) ekleyerek yapar.
Bugün çıkması gereken yeni v8 motoru bunu destekliyor (sanırım)
Ham Javascript'te yapabileceğiniz en iyi şey, birkaç eşzamansız çağrıyı (xmlhttprequest) kullanmaktır, ancak bu gerçekten iş parçacığı ve çok sınırlı değildir. Google Gears , tarayıcıya bazıları iplik geçirme desteği için kullanılabilecek bir dizi API ekler.
Herhangi bir AJAX öğesi kullanamazsanız veya istemiyorsanız, bir iframe veya on kullanın! ;) Çapraz tarayıcı ile karşılaştırılabilir sorunlar veya dot net AJAX vb. İle sözdizimi sorunları hakkında endişelenmeden iframe'lerde ana sayfaya paralel olarak çalışan işlemleriniz olabilir ve ana sayfanın JavaScript'ini (içe aktardığı JavaScript dahil) iframe.
Örneğin, bir üst iframe'de, egFunction()
iframe içeriği yüklendikten sonra üst belgeyi aramak için (bu eşzamansız bölümdür)
parent.egFunction();
Dinamik olarak iframe'leri de oluşturun, böylece isterseniz ana html kodu bunlardan arınmış olur.
Başka bir olası yöntem, javascript ortamında bir javascript yorumlayıcısı kullanmaktır.
Birden çok yorumlayıcı oluşturarak ve bunların ana iş parçacığından yürütülmesini denetleyerek, her iş parçacığının kendi ortamında çalıştığı çoklu iş parçacığını simüle edebilirsiniz.
Yaklaşım web çalışanlarına biraz benzer, ancak tercümanın tarayıcı küresel ortamına erişmesini sağlarsınız.
Bunu göstermek için küçük bir proje yaptım .
Bu blog yazısında daha ayrıntılı bir açıklama .
Javascript'te konu yok, ancak işçiler var.
Paylaşılan nesnelere ihtiyacınız yoksa işçiler iyi bir seçim olabilir.
Tarayıcı uygulamalarının çoğu, aslında tüm çekirdekleri kullanmanıza olanak tanıyarak çalışanları tüm çekirdeklere yayar. Bunun bir demosunu burada görebilirsiniz .
Bunu çok kolaylaştıran task.js adında bir kütüphane geliştirdim .
task.js Tüm çekirdeklerde (node.js ve web) CPU yoğun kod çalıştırmak için basitleştirilmiş arayüz
Bir örnek
function blocking (exampleArgument) {
// block thread
}
// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);
// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});
HTML5 spesifikasyonu ile aynı için çok fazla JS yazmanız veya bazı hack'ler bulmanız gerekmez.
HTML5'te sunulan özelliklerden biri de Web Çalışanları diğer komut dosyalarından bağımsız olarak, sayfanın performansını etkilemeden arka planda çalışan JavaScript olan .
Hemen hemen tüm tarayıcılarda desteklenir:
Chrome - 4.0+
IE - 10.0+
Mozilla - 3.5+
Safari - 4.0+
Opera - 11.5+