2016-09-05 2 views
1

이 코드를 실행한다고 가정 해 보겠습니다.자바 스크립트의 데이터 종족?

var score = 0; 
for (var i = 0; i < arbitrary_length; i++) { 
    async_task(i, function() { score++; }); // increment callback function 
} 

은 이론적으로 난이 데이터 인종과 하나의 증가가 발생할 수 있습니다 동시에 증가하는 것을 시도 두 개의 스레드를 제공 이해하지만, nodejs (자바 스크립트)는 단일 스레드 것으로 알려져있다. 점수의 최종 값이 arbitrary_length와 동일하다는 것을 보장합니까?

답변

1

노드는 이벤트 루프를 사용합니다. 이것을 큐로 생각할 수 있습니다. 따라서 우리는 for 루프가 function() { score++; } 콜백 arbitrary_length 번을이 큐에 넣는다 고 가정 할 수 있습니다. 그 다음에 js 엔진이 이들을 하나씩 실행하고 매번 score을 증가시킵니다. 그래. 콜백이 호출되지 않거나 score 변수가 다른 곳에서 액세스되는 유일한 예외입니다.

실제로이 패턴을 사용하여 작업을 병렬 처리하고 결과를 수집하고 모든 작업이 완료 될 때 단일 콜백을 호출 할 수 있습니다.

var results = []; 
for (var i = 0; i < arbitrary_length; i++) { 
    async_task(i, function(result) { 
      results.push(result); 
      if (results.length == arbitrary_length) 
       tasksDone(results); 
    }); 
} 
+0

"모든 작업이 완료되면 단일 콜백을 호출하십시오."모든 작업이 완료되었음을 어떻게 알 수 있습니까? 나는 바쁜 시간을 기다릴 때까지만 생각할 수 있습니다! = arbitrary_length – bilalba

+0

@bilalba 네, 그게 전부입니다. 실제로이 코드는 결과 배열 길이와 정확히 동일합니다. :-) 그것은 더 나은 btw. 거부 된 작업에 대해 일종의 오류 처리기를 추가하십시오. 그렇지 않으면 오류가 발생할 때 영원히 기다립니다. – inf3rno

+1

"모든 작업이 완료되었음을 어떻게 알 수 있습니까?"- 이것이 Promise.all()이 구현 된 정확한 이유입니다. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all –

1

동시에 두 함수를 호출 할 수 없으므로 (b/c 노드가 단일 스레드이므로) 문제가되지 않습니다. 유일한 문제는 async_task (..)가 콜백을 삭제하는 경우입니다. 그러나 예를 들어, 'async_task (..)'가 주어진 함수로 setTimeout (..)을 호출했다면, 각각의 호출이 실행될 것이고, 그들은 서로 충돌하지 않을 것이고, 'score'는 예상 값을 가질 것입니다 , 'arbitrary_length', 끝에.

물론, 'arbitrary_length'는 메모리를 소모하거나 콜백을 보유하고있는 콜렉션이 오버 플로우 한만큼 커질 수 없습니다. 그러나 스레딩 문제는 없습니다.

2

오전 나는 점수의 최종 값이 arbitrary_length에 동일 해집니다 보장?

예, 모든 async_task() 호출이 콜백을 한 번만 호출하는 한 점수의 최종 값이 arbitrary_length와 같음을 보장합니다.

Javascript가 정확히 동일한 시간에 실행되는 절대 두 가지가없는 것을 보장하는 Javascript의 단일 스레드 속성입니다. 대신 브라우저와 node.j 모두에서 이벤트가 주도하는 Javascript 특성으로 인해 하나의 JS가 완료 될 때까지 실행되고 다음 이벤트는 이벤트 대기열에서 가져와 콜백을 트리거하여 완료까지 실행합니다.

인터럽트 기반 Javascript와 같은 것은 없습니다 (일부 콜백은 현재 실행중인 다른 Javascript의 일부를 인터럽트 할 수 있습니다). 모든 것이 이벤트 대기열을 통해 직렬화됩니다. 이는 엄청난 단순화이며 여러 스레드가 동시에 실행되거나 구동 코드를 인터럽트 할 때 안전하게 프로그래밍하기 위해 많은 노력이 필요한 많은 상황을 방지합니다.

일부 동시성 문제가 여전히 염려되지만, 여러 비동기 콜백이 모두 액세스 할 수있는 공유 상태와 더 관련이 있습니다. 주어진 시간에 단 하나의 비동기 연산에만 액세스 할 수 있지만 여러 비동기 연산을 포함하는 코드를 사용하면 일부 비동기 연산이 여러 비동기 연산의 중간에있는 동안 "중간"상태로 남을 수 있습니다 다른 비동기 작업이 실행될 수 있고 해당 데이터에 액세스하려고 시도 할 수있는 지점.

자바 스크립트의 이벤트 중심 특성에 대한 자세한 내용은 How does JavaScript handle AJAX responses in the background?을 읽고 그 대답에는 여러 가지 참조가 포함되어 있습니다.

그리고 가능 공유 데이터 경쟁 조건의 종류에 대해 설명 다른 유사한 답 : Can this code cause a race condition in socket io?

일부 다른 참조 :

how do I prevent event handlers to handle multiple events at once in javascript?

Do I need to be concerned with race conditions with asynchronous Javascript?

JavaScript - When exactly does the call stack become "empty"?

,626을


여기 내 자신의 코드에서 예입니다, 심지어 스레드없이 인터럽트없이 당신에게 자바 스크립트에서 일어날 수있는 동시성 문제 (의 아이디어를 제공합니다.

저는 집에서 다락방 팬을 제어하는 ​​Raspberry Pi node.js 서버를 가지고 있습니다. 10 초마다 두 개의 온도 프로브를 점검합니다. 하나는 다락방 내부, 다른 하나는 집 외부에 있으며 릴레이를 통해 팬을 제어하는 ​​방법을 결정합니다. 또한 차트로 표시 할 수있는 온도 데이터를 기록합니다. 한 시간에 한 번씩, 정전 후 또는 서버 충돌시 메모리에 수집 된 최신 온도 데이터를 일부 파일에 저장합니다. 저장 작업에는 일련의 비동기 파일 쓰기 작업이 포함됩니다. 이러한 비동기 쓰기 각각은 시스템에 제어를 반환하고 비동기 콜백이 신호 완료라고 할 때 계속됩니다. 이것은 낮은 메모리 시스템이며 데이터가 잠재적으로 사용 가능한 RAM의 상당 부분을 차지할 수 있기 때문에 데이터는 쓰기 전에 메모리에 복사되지 않습니다 (실용적이지는 않습니다). 따라서 라이브 인 메모리 데이터를 디스크에 씁니다.

언제든지 이러한 비동기 파일 I/O 작업 중 많은 파일 쓰기 완료를 나타내는 콜백을 기다리는 동안 서버의 타이머 중 하나가 작동 할 수 있으며 새로운 세트를 수집 할 수 있습니다 메모리의 데이터 세트를 수정하려고 시도 할 것입니다. 그것은 동시 발생 문제가 발생하기를 기다리고 있습니다. 필자가 데이터의 일부를 쓰는 동안 데이터를 변경하고 나머지를 쓰 기 전에 쓰기가 끝나기를 기다리는 경우 데이터의 한 부분을 작성 했으므로 쓰여지는 데이터가 쉽게 손상 될 수 있습니다. 데이터가 저 아래에서 수정 된 다음 변경되지 않았다는 것을 알지 못하고 더 많은 데이터를 쓰려고 시도합니다. 그것은 동시성 문제입니다.

실제로이 동시성 문제가 내 서버에서 발생하고 (내 코드에서 안전하게 처리되는 경우) 명시 적으로 기록하는 console.log() 문이 있습니다. 며칠에 한 번 내 서버에서 발생합니다. 나는 그것이 그것이 실제로 있다는 것을 안다.

이러한 유형의 동시성 문제를 해결하는 데는 여러 가지 방법이 있습니다. 가장 간단한 방법은 모든 데이터를 메모리에 복사 한 다음 복사본을 작성하는 것입니다. 쓰레드 나 인터럽트가 없기 때문에 메모리에 복사본을 만들면 동시성 (concurrency) 문제가 발생하지 않도록 복사본 중간에서 비동기 연산을 수행하지 않아도됩니다. 그러나,이 경우에는 실용적이지 않았습니다. 그래서 큐를 구현했습니다. 필기를 시작할 때마다 데이터를 관리하는 개체에 플래그를 설정합니다. 그런 다음 플래그가 설정되어있는 동안 저장된 데이터의 데이터를 추가하거나 수정하려고 할 때마다 이러한 변경 사항이 대기열로 이동합니다. 해당 플래그가 설정되어있는 동안 실제 데이터는 건드리지 않습니다. 데이터가 디스크에 안전하게 기록되면 플래그가 재설정되고 대기중인 항목이 처리됩니다. 모든 동시성 문제는 안전하게 방지되었습니다.


그래서 이것은 염려해야하는 동시성 문제의 예입니다.Javascript의 단순한 가정은 하나의 Javascript가 의도적으로 컨트롤을 시스템으로 돌려 보내지 않는 한 인터럽트가 발생하지 않고 완료까지 실행된다는 것입니다. 따라서 의식적으로 시스템을 다시 제어 할 때를 제외하고는 코드가 중단되지 않으므로 위에서 설명한 많은 동시성 문제를 훨씬 쉽게 처리 할 수 ​​있습니다. 이것이 우리가 자바 스크립트에서 mutex와 세마포어와 같은 것을 필요로하지 않는 이유입니다. 필요한 경우 위에서 설명한 간단한 플래그 (그냥 일반적인 자바 스크립트 변수)를 사용할 수 있습니다.


완전히 동기적인 자바 스크립트에서는 다른 자바 스크립트가 중단되지 않습니다. Javascript의 동기 부분은 이벤트 대기열의 다음 이벤트가 처리되기 전에 완료까지 실행됩니다. 이것은 Javascript가 "이벤트 중심"언어라는 것을 의미합니다. 자바 스크립트의 현재 조각이 실행을 완료 할 때까지 타이머 이벤트를 처리 할 수 ​​없습니다

A 
C 
D 
B 

:

console.log("A"); 
// schedule timer for 500 ms from now 
setTimeout(function() { 
    console.log("B"); 
}, 500); 

console.log("C"); 

// spin for 1000ms 
var start = Date.now(); 
while(Data.now() - start < 1000) {} 

console.log("D"); 

당신은 콘솔에서 다음을받을 것이다 :이의 예를 들어, 당신이 경우이 코드를했다 , 비록 그것이 이벤트 큐에 더 빨리 추가되었을지라도. JS 인터프리터가 작동하는 방식은 시스템에 제어권을 반환 한 다음 (그리고 그때 만) 현재의 JS를 실행하여 이벤트 대기열에서 다음 이벤트를 가져오고 해당 이벤트와 관련된 콜백을 호출하는 것입니다.

여기에는 일련의 사건이 있습니다.

  1. 이 JS가 실행되기 시작합니다.
  2. console.log("A")이 출력됩니다.
  3. 타이머 이벤트는 지금부터 500ms 일정입니다. 타이머 하위 시스템은 원시 코드를 사용합니다.
  4. console.log("C")이 출력됩니다.
  5. 코드가 스핀 루프에 들어갑니다.
  6. 스핀 루프의 중간 지점에서 이전에 설정된 타이머가 작동 할 준비가되었습니다. 이것이 어떻게 작동 하는지를 정확히 결정하는 것은 인터프리터 구현에 달려 있지만, 최종 결과는 타이머 이벤트가 Javascript 이벤트 큐에 삽입된다는 것입니다.
  7. 회전 루프가 완료됩니다.
  8. console.log("D")이 출력됩니다.
  9. 이 자바 스크립트 조각이 끝나고 컨트롤이 다시 시스템에 반환됩니다.
  10. Javascript 인터프리터는 현재 대기중인 이벤트가 있는지 기다리기 위해 이벤트 대기열을 검사하도록 현재 Javascript가 완료되었음을 확인합니다. 타이머 이벤트와 해당 이벤트와 관련된 콜백을 찾고 콜백 (JS 실행의 새 블록 시작)을 호출합니다. 해당 코드가 실행되고 console.log("B")이 출력됩니다.
  11. 해당 setTimeout() 콜백이 실행을 끝내고 인터프리터는 다시 실행할 준비가 된 다른 이벤트가 있는지 확인하기 위해 이벤트 큐를 검사합니다.
+0

여러 참조가 추가되었습니다. – jfriend00

+0

이것은 매우 통찰력있는 답변입니다. 정말 고마워요! – bilalba

+0

"여전히 염려해야 할 경쟁 조건이 있지만 여러 비동기 콜백이 모두 액세스 할 수있는 공유 상태와 관련이 있습니다." <- 나는 이것을 경쟁 조건이라고 부르지 않고 비동기 콜백을 순차적으로 (예를 들어'Promise.then()'체인을 사용하여 실행하는 방식으로 명령합니다. 3 가지 기능이있는 것과 같습니다. 하나는 파일이 있는지, 하나는 파일을 열 것인지, 다른 하나는 파일의 내용에 액세스하고 있는지 확인하는 것과 같습니다. 두 번째 경기 전에 마지막 경기를 먼저 치면 경기 조건으로 간주되지 않습니다. –