在Javascript中证明睡眠功能是不可能的?

时间:2014-06-03 13:40:27

标签: javascript multithreading

暂且讨论睡眠功能的效用,我只想知道(更有可能确认)Javascript中不可能有睡眠功能。

但当然我需要限制用于构建睡眠功能的资源。睡眠功能的含义满足以下条件:

  1. 语法必须简单:

    /*doing something before sleep*/
    sleep(int milliseconds);//completely pause the execution
    /*doing something after sleep*/
    
  2. 我注意到我们可以使用"从其他线程获取更新"的技巧,例如使用new Date()来检查终止循环的条件。所以任何类似的行为,即让线程进入由布尔变量控制的循环,并且布尔变量取决于某些其他线程的信息,例如,系统时间,服务器时间等,应该超出讨论的范围。因此,强加的约束应该是:仅使用Javascript的本机功能构建睡眠功能,这意味着时间的唯一资源基本上是setTimeout()

  3. 嗯,直觉上我觉得构建这样的函数是不可能的,因为单个线程只能通过循环暂停。但是,使用单线程执行时,无法在循环内更改布尔变量(这决定了退出循环的条件)。

    但这显然只是一种感觉,所以我只是想知道,有没有讨论JS中这种功能的实际可能性?

4 个答案:

答案 0 :(得分:1)

这不是一个好问题。 JavaScript 是类似语言的通用词。想象(甚至创建)具有sleep函数的JavaScript实现非常简单(尽管无用)。这是一回事。

其次setTimeout确实使用系统/服务器时间。你不能没有另一个。所以你的问题的答案是:是的,只是循环时间检查(再次:完全没用)。你的第二个限制是不现实的。

真正的限制应该是:不使用100%CPU的睡眠。在这种情况下,任何主要的JS实现都不可能。

所有这些都是在同步睡眠的背景下。但是setTimeout 睡眠。它只是异步的。为什么这是一个问题?

答案 1 :(得分:1)

使用Promise和新的ES6生成器,这几乎可以完成。看起来像这样:

sync(function* (){
    yield sleep(3000);
    console.log('more sleep');    
    yield sleep(3000);
    console.log('done sleeping');
});

完整代码:

function sync(generator){
    var _generator = generator();

    function done(){
        var result = _generator.next().value;
        if(result instanceof Promise){
            result.then(done);
        }
    }

    done();
}

sync(function* (){
    yield sleep(3000);
    console.log('more sleep');    
    yield sleep(3000);
    console.log('done sleeping');
});

function sleep(ms){
    return new Promise(function(res, rej){
        setTimeout(res, ms);
    });
}

JsFiddle demo。 (浏览器必须support JS Harmony才能实现此功能)

这实际上并不阻止Javascript事件循环/线程。它只是暂停执行传递给sync的函数,并在超时完成后恢复它。

答案 2 :(得分:1)

@levi:你的例子现在是“原生的”Javascript :)至少如果它被定义为ECMAscript 2015,那本周终于被批准了,包括函数*,yield *和yield语句。

这里我将展示如何从多个级别的函数调用 sleep()。这个例子用THREE.js绘制一个树(就像眼睛糖果一样),并使用sleep(),这样你就可以在构建时看到它。旋转场景,相机,灯光,着色器和渲染循环的三个东西隐藏在setup3D()中。

所有可以暂停的函数必须定义为GeneratorFunctions,简单地写为函数*。首次调用时,它返回一个GeneratorObject,但不执行该函数的主体。它开始“睡觉”可以这么说。 .next()方法将开始执行主体,直到下一个yield语句。

函数* sleep()设置wakup调用的计时器,然后产生(暂停)直到wakeup执行.next()。当它从yield中唤醒时,它会像正常函数一样返回,所有本地数据都保持不变。

主函数* drawTree()绘制一个分支,暂停,然后分成3个部分,每个部分是一个新的较小的树。一棵很小的树只是一片叶子。 GeneratorFunction可以使用yield *将执行委托给另一个GeneratorFunction。它的工作方式就像一个简单的函数调用,但它可以暂停产生。

site = Setup3D(20);
sleepingTask = new drawTree(site.center, 7);
sleepingTask.next();

function* sleep(delay, task) {
    task = task || sleepingTask;
    var wakeup = function() {task.next()};
    setTimeout(wakeup, delay*1000);
    yield;
}

function* drawTree(root, size) {
    if (size<2) drawLeaf(root)
    else yield* drawBranch(root, size);
}

function* drawBranch(stem, size) {
    stem.add(new THREE.Mesh(new THREE.CylinderGeometry(size/12, size/10, size), 
        site.color(0.5,0.3,0.2)) .translateY(size/2));
    stem.add(new THREE.Mesh(new THREE.SphereGeometry(size/12), 
        site.color(0.5,0.3,0.2)) .translateY(size));
    for (var r of [1, 3, 5]) {
        var newStem = new THREE.Object3D();
        stem.add(newStem.translateY(size).rotateY(r).rotateX((20-r)/20));
        yield* sleep(0.1);
        yield* drawTree(newStem, size*(70+r)/100);
    }
}

function drawLeaf(stem) {
    stem.add(new THREE.Mesh(new THREE.CylinderGeometry(0, 0.1, 3), 
        site.color(0,1,0)) .rotateX(0.3).translateY(1.5));
    stem.add(new THREE.Mesh(new THREE.CircleGeometry(1), 
        site.color(0,1,0)) .rotateX(0.3).translateY(2));
}
使用sleep()也可以使用

多任务处理。这里drawLeaf()被扩展,因此每个叶子成为一个单独的任务。每片叶子都有自己的生命,它会生长,坐下,褪色和落下。叶子的GeneratorObject被添加到词干对象,并被用作第二个睡眠参数,因此回调知道要唤醒谁。函数* leafLife()是从drawLeaf()开始的,但drawLeaf()本身可以定义为一个简单的函数,因为它不使用yield或yield *。

function drawLeaf(stem) {
    stem.add(new THREE.Mesh(new THREE.CylinderGeometry(0, 0.02, 0.6), 
        site.color(0,1,0)) .rotateX(0.3).translateY(0.3));
    stem.add(new THREE.Mesh(new THREE.CircleGeometry(1/5), 
        site.color(0,1,0)) .rotateX(0.3).translateY(2/5));
    stem.leafTask = new leafLife(stem);
    stem.leafTask.next();
}

function* leafLife(stem) {
    for (var i=0; i++<9;)    { 
        yield* sleep(0.5, stem.leafTask);  
        stem.scale.multiplyScalar(1.2);
    }
    yield* sleep(10 + 30*Math.random(), task);
    for (var i=0; i++<25;)   { 
        yield* sleep(1, stem.leafTask);    
        stem.children[1].material.color.setRGB(i/25, 1-i/40, 0);
    }
    for (var i=0; i++<100;)  { 
        yield* sleep(0.05, stem.leafTask); 
        stem.translateY(-2).rotateX(0.3).rotateY(0.5);
    }
    stem.visible = false;
}

完整代码,以防您想尝试自己:

site = Setup3D(20);
sleepingTask = new drawTree(site.center, 7);
sleepingTask.next();

function* sleep(delay, task) {
  task = task || sleepingTask;
  var wakeup = function() {
    task.next()
  };
  setTimeout(wakeup, delay * 1000);
  yield;
}

function* drawTree(root, size) {
  if (size < 2) drawLeaf(root)
  else yield* drawBranch(root, size);
}

function* drawBranch(stem, size) {
  stem.add(new THREE.Mesh(new THREE.CylinderGeometry(size / 12, size / 10, size),
    site.color(0.5, 0.3, 0.2)).translateY(size / 2));
  stem.add(new THREE.Mesh(new THREE.SphereGeometry(size / 12),
    site.color(0.5, 0.3, 0.2)).translateY(size));
  for (var r of[1, 3, 5]) {
    var newStem = new THREE.Object3D();
    stem.add(newStem.translateY(size).rotateY(r).rotateX((20 - r) / 20));
    yield* sleep(0.1);
    yield* drawTree(newStem, size * (70 + r) / 100);
  }
}

function drawLeaf(stem) {
  stem.add(new THREE.Mesh(new THREE.CylinderGeometry(0, 0.02, 0.6),
    site.color(0, 1, 0)).rotateX(0.3).translateY(0.3));
  stem.add(new THREE.Mesh(new THREE.CircleGeometry(1 / 5),
    site.color(0, 1, 0)).rotateX(0.3).translateY(2 / 5));
  stem.leafTask = new leafLife(stem);
  stem.leafTask.next();
}

function* leafLife(stem) {
  var i, task = stem.leafTask;
  for (i = 0; i++ < 9;) {
    yield* sleep(0.5, task);
    stem.scale.multiplyScalar(1.2);
  }
  yield* sleep(10 + 30 * Math.random(), task);
  for (i = 0; i++ < 25;) {
    yield* sleep(1, task);
    stem.children[1].material.color.setRGB(i / 25, 1 - i / 40, 0);
  }
  for (i = 0; i++ < 100;) {
    yield* sleep(0.05, task);
    stem.translateY(-2).rotateX(0.3).rotateY(0.5);
  }
  stem.visible = false;
}

function Setup3D(rotationsPerMinute) {
  var p = {};
  p.scene = new THREE.Scene();
  p.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  p.camera.position.z = 50;
  p.camera.position.y = 30;
  p.renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
  });
  p.renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(p.renderer.domElement);

  var directionalLight = new THREE.DirectionalLight(0xffffaa, 0.7);
  directionalLight.position.set(-1, 2, 1);
  p.scene.add(directionalLight);
  p.scene.add(new THREE.AmbientLight(0x9999ff));

  p.center = new THREE.Object3D();
  p.scene.add(p.center);
  p.color = function(r, g, b) {
    return new THREE.MeshLambertMaterial({
      color: new THREE.Color(r, g, b),
      vertexColors: THREE.FaceColors,
      side: THREE.DoubleSide
    });
  };

  var render = function() {
    requestAnimationFrame(render);
    p.renderer.setSize( window.innerWidth, window.innerHeight );
    p.scene.rotateY(rotationsPerMinute / 60 / 60);
    p.renderer.render(p.scene, p.camera);
  };
  render();

  return p;
}
body {
  margin: 0;
  overflow: hidden;
  background-color: #a0a0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>

答案 3 :(得分:0)

简单地说 - 不,你不可能有一个全球性的睡眠&#34;样式函数将阻止Javascript在浏览器窗口中运行。

好的,这是简单的答案,更复杂的是它完全取决于你想要实现的目标。你实现可以模拟睡眠的方法就是拥有一个运行主逻辑的函数,它使用setTimeout调用自身(你需要记录一个数据以确保你的时间是正确的),然后你可以延迟在睡眠期间自行调用。

然而,这是纯粹的情境,你必须确保将所有逻辑编码到单个函数中,因为其他东西可以并行运行。