줄리아 병렬 컴퓨팅 노트 (1)

이 기사는 "Julia Language Programming"(Wei Kun) 14 장의 읽기 노트로 많은자가 테스트 및 공식 문서를 추가합니다. 내용물은 기본적으로 완전히 덮혀 있지만 원래에 비해 맛이 더 좋습니다. 전체 텍스트는 매우 길며 다섯 부분으로 나뉘어져 있으며 이것이 첫 번째입니다.

하나, 프로세스, 스레드

프로세스는 스레드의 아버지입니다. 프로세스는 운영 체제에서 리소스를 얻을 수있는 프로그램의 실행 인스턴스이며 리소스 할당의 최소 단위입니다. 프로세스는 스레드의 상위이며 여러 스레드를 포함 할 수 있으며 그 중 주 스레드가 있어야합니다. 스레드는 프로세스의 종속 항목이며 프로세스에서 리소스를 가져옵니다. 스레드는 여러 분기를 따라 흐르는 강과 같이 프로세스의 다른 실행 경로이므로 여러 하위 스레드가 동일한 상위 프로세스의 리소스를 공유합니다.

하드웨어 측면에서 단일 CPU 코어는 하나의 프로세스 만 실행할 수 있습니다. 여러 프로세스를 실행해야하는 요구 사항을 충족하기 위해 운영 체제는 소위 "프로세스 스케줄링"을 사용하여 여러 프로세스가 번갈아 실행되도록 정렬하는 "스케줄링 테이블"을 생성합니다. 이는 동시에 실행되는 것처럼 보이는 "의사 동시성"이라고합니다. 따라서 단일 코어 컴퓨터는 Word를 열기 전에 QQ 프로세스가 종료 될 때까지 기다리는 대신 Word를 작성하는 동안 QQ와 채팅 할 수도 있습니다. 동시성을 더욱 향상시키기 위해 (동시성을 개선하면 CPU를 최대한 활용하는 데 도움이 됨) 최신 운영 체제는 코어를 여러 "가상 코어"로 분해하는 것과 동일한 스레드 개념을 도입했으며 각 가상 코어는 "스레드"를 실행합니다. 프로세스가 여러 스레드로 분해 된 후 하나의 코어에 국한되지 않고 여러 코어에 분산 될 수 있습니다. 그러나 이것은 또한 CPU 자체의 지원이 필요합니다. Intel CPU는 하이퍼 스레딩 기술이라고하는 이러한 종류의 분해를 지원하며 물리적 코어는 두 개의 가상 코어로 나눌 수 있습니다. 초기에는 AMD CPU를 분해 할 수 없었기 때문에 유사한 동시 성능을 달성하기 위해 더 많은 물리적 코어를 스택해야했지만 최근 Ryzen 시리즈에도 하이퍼 스레딩이있어 좋아 보입니다.

Julia에서 Distributed패키지는 병렬화 및 분산 기능을 제공합니다. 그러니 모든 것이 시작되기 전에 using Distributed.

Julia가 현재 제공하는 것은 주로 프로세스 수준과 코 루틴 수준 (다음 섹션에서) 병렬입니다. 프로세스는 PID 번호가 표시된 "로컬 프로세스"와 "원격 프로세스"로 나뉩니다. 주 프로세스 PID = 1이라고하는 단 하나의 로컬 프로세스가 있습니다. 원격 프로세스를 작업 자라고합니다. 기본적으로 julia 명령으로 열린 REPL에는 하나의 기본 프로세스 만 있습니다. addprocs(N)N 개의 작업자 추가하면 해당 숫자는 PID = 2,3,4, ..., N + 1이거나 rmprocs(k)k 번째 작업자를 삭제합니다. Worker가 삭제되었는지 여부에 관계없이 새로 추가 된 Worker는 N + 2부터 번호가 지정됩니다. 또한 터미널에서 julia -p n명령 을 사용하여 n Workers로 REPL 직접 열 수 있으며 -p매개 변수는 자동으로 Distributed패키지를 로드합니다 .

를 사용하여 procs()모든 프로세스의 PID와 workers()모든 작업자 의 PID 수 있습니다 . 프로세스 및 작업자 수를 사용 nprocs()하고 nworkers()확인합니다. 일반적으로 nprocs()=nworkers()+1. 그러나 원격 프로세스가없는 경우 메인 프로세스는 작업자로 간주되므로 nworkers()=nprocs()=1.

Julia는 스레드 수준 병렬 처리의 테스트 버전도 제공합니다. 여러 스레드 사이에 "조건 변수"라고하는 "조정자"가 있어야합니다. 조건 변수는 모든 스레드에 표시되는 전역 변수이며 상태를 변경할 수 있습니다. 각 스레드에 대기 또는 실행을 알리는 공개 방송 신호와 동일합니다. 조건 변수는 모든 스레드에 의해 수정되므로 충돌도 발생할 수 있으므로 일반적으로 소위 뮤텍스와 함께 사용됩니다. 뮤텍스는 조건 변수의 "비서"와 동일하며 방문하는 모든 스레드를 수신하여 한 번에 하나의 스레드 만 조건 변수를 터치하도록합니다. 상호 배제 원칙에 관해서는 더 난해하다.

둘째, 코 루틴

코 루틴은 프로세스도 스레드도 아니지만 일종의 "가상, 패치 워크 작은 스레드"로 이해 될 수 있습니다. 프로세스를 자동차의 흐름과 비교하면 스레드는 자동차입니다. 백 대의 자동차를 1000 개의 "소형 자동차"로 나누면이 "소형 자동차"가 원래 차량보다 더 효율적으로 작동합니다. 이러한 "자동차"를 코 루틴이라고합니다. 일반적으로 코 루틴은 스레드보다 작고 실행 효율성이 높으며 사용하기가 더 쉽기 때문에 "사용자 모드 경량 스레드"또는 "녹색 스레드"라고도합니다.

요약하면 프로세스, 스레드 및 코 루틴은 세 가지 수준의 병렬 메커니즘입니다. 각 병렬 메커니즘은 전환시 추가 시간 비용을 소비하며 소비 순서는 process> thread> coroutine입니다. 우리는 필요에 따라 적절한 수준의 병렬 처리를 선택해야합니다. 현재 Julia는 스레드 사용을 권장하지 않습니다.

Julia에서는 코 루틴을 Task라고합니다. 태스크를 생성하는 방법에는 두 가지가 있습니다.

  • taskname=Task(f)
    Task(f)- 생성 방법 을 사용하여 함수 객체 f를 taskname이라는 Task로 캡슐화합니다. 이때 f는 매개 변수가 없어야합니다. 즉, 매개 변수가 없거나 모든 매개 변수에 기본값이 있어야합니다. f에 매개 변수가 있으면 f가 표현식 Task()이되고 전달되기 전에 실행되므로 전달 Task()되는 것은 더 이상 함수 객체가 아니라 실행의 결과입니다. 이 문제를 해결하려면 하나를 정의한 f1()=f(参数)다음 taskname=Task(f1).
  • taskname=@task 表达式
    @task- 매크로 명령 을 사용하여 식을 taskname이라는 Task로 캡슐화합니다. 대문자 사용에주의하십시오.

생성되면 사용 가능 istaskstarted(taskname)하며 istaskdone(taskname)작업 시작 및 종료 여부를 확인합니다. 작업에는 실행 가능 (시작 가능), 대기 (차단 대기), 대기 (예약 중), 완료 (실행 종료), 실패 (실행이 비정상적으로 종료 됨)의 5 가지 상태가 있습니다. 작업 실행 대기열을 유지 관리하는 Julia 내부에 스케줄러가 있습니다. 사용자 schedule(taskname)는 작업을 대기열에 추가하고 시작하면 자동으로 완료 상태로 돌아가 작업이 완료되었음을 나타냅니다.

표현식의 경우 "결합 된"매크로 명령이 @async 表达式있습니다.. 태스크를 생성하고 직접 시작합니다. 예 :

julia> using Distributed

julia> a=zeros(1,5)
1×5 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0

julia> @async fill!(a,4)
Task (done) @0x00000000063059f0

julia> a
1×5 Array{Float64,2}:
 4.0  4.0  4.0  4.0  4.0

전달 된 Task()함수 객체 f 내부의 특정 명령을 사용하여이 태스크가 상태를 변경하도록 할 수 있습니다.

sleep(N)  睡眠Nyield()  请求切换为其他task
yieldto(taskname)  请求切换为指定的task,一般不建议使用

세, 채널

채널은 차단 특성이있는 선입 선출 대기열이며, 이는 두 개의 개구부가있는 파이프와 동일합니다. 선언 방법은 다음과 같습니다.

channelname=Channel{类型}(大小)

여기서는 채널 "요소"에 배치 된 객체를 호출합니다. 채널이 선언 될 때 요소 유형이 지정되지 않은 경우 기본값은 모든 유형입니다.

채널을 만든 후를 사용하여 put!(channelname,元素)요소를 채널 take!(channelname)에 넣고을 사용 하여 채널에서 최상위 요소를 추출합니다. take!()요소가 채널에서 제거됩니다. 제거하지 않으려면 fetch()대신 사용할 수 있습니다 (페치에 느낌표가 없습니다). 채널이 꽉 차거나 비어 있으면 put!()/take!()차단 (즉, 실행 중지)되며 이때 요소 put!()/take!()추출 / 입력 하여 차단 을 해제 할 수 있습니다 . for 구조를 사용하여 트래버스하여 채널의 요소를 추출 할 수도 있습니다. 예를 들면 다음과 같습니다.

julia> c=Channel(2)
Channel{Any}(sz_max:2,sz_curr:0)
   
julia> put!(c,10)
10
    
julia> put!(c,11)
11
    
julia> for x in c
			print(x," ")
	   end
10 11

여기서 for 구조는 본질적으로 일련의 자동 실행 take!()이며 take!()요소도 제거합니다. 채널이 비워지면 for 구조도 차단됩니다.

사용 close(channelname)은 채널의 입구를 닫고 모든 것을 해제 put!()하고 take!()차단 하도록 강제 할 수 있습니다 . 지금은 더 이상 요소를 추가 할 수 없지만 모든 요소를 ​​가져올 때까지 여전히 추출 할 수 있습니다.

Channel은 여러 Task에서 동시에 안전하게 읽고 쓸 수있는 공통 버퍼로, 여러 Task 사이에 요소를 배치 / 추출하는 순서는 임의적입니다. 예를 들어 함수 또는 작업을 사용하여 10 개의 요소를 채널에 넣은 다음 2 개의 작업을 시작하여 요소를 병렬로 추출 할 수 있습니다. 이 두 작업은 완료 될 때까지 스케줄러의 배열에 따라 임의의 순서로 번갈아 추출됩니다. 이 임의 순서의 특성은 추출 할 총 요소 수가 Task 함수 내에서 명시 적으로 지정되지 않는 한 각 Task가 반드시 정확히 5 개의 요소를 가져 오지 않는다는 것을 결정합니다.

이 예제에서 우리는 요소를 생산자로 두는 함수 또는 작업을 호출하고 요소를 추출하는 작업을 소비자라고하는데 이는 소위 "생산자 / 소비자 문제"로 요약됩니다. 채널은 시장과 동일합니다. 사실 소비자는 동시에 생산자가되어 서로 데이터를 생산하고 교환 할 수 있기 때문에 일방향 거래를 제공 할뿐만 아니라 따라서 채널은 병렬 처리를 위해 설계된 편리하고 안전한 데이터 교환 영역입니다.

물론 여기에 내재 된 문제는 퍼팅 및 추출의 적절한 순서를 보장하는 방법입니다. 우선 채널 순서가 지정되고 선입 선출 원칙에 따라 작업을 설계 할 수 있습니다. 차단 기능은 삽입과 추출이 자동으로 대기하게하여이를 이용하여 적절한 순서를 설계 할 수 있습니다. 둘째, wait()함수는 순서를 조정하는 데 도움이 될 수 있습니다.

wait([x])

Block the current task until some event occurs, depending on the type of the argument:
  * [`Channel`](@ref): Wait for a value to be appended to the channel.
  * [`Condition`](@ref): Wait for [`notify`](@ref) on a condition.
  * `Process`: Wait for a process or process chain to exit. The `exitcode` field of a process can be used to determine success or failure.
  * [`Task`](@ref): Wait for a `Task` to finish. If the task fails with an exception, the exception is propagated (re-thrown in the task that called `wait`).
  * [`RawFD`](@ref): Wait for changes on a file descriptor (see the `FileWatching` package).
If no argument is passed, the task blocks for an undefined period. A task can only be restarted by an explicit call to [`schedule`](@ref) or [`yieldto`](@ref).
Often `wait` is called within a `while` loop to ensure a waited-for condition is met before proceeding.

wait(r::Future)

Wait for a value to become available for the specified [`Future`](@ref).

wait(r::RemoteChannel, args...)

Wait for a value to become available on the specified [`RemoteChannel`](@ref).

마지막으로 "채널 바인딩"이라는 "1 단계"기술을 소개합니다. 두 단계로 나뉩니다.

  1. 매개 변수 (가상 매개 변수)와 Channel 만 사용하여 생산자 함수를 선언합니다 p().
  2. 매개 변수 (실제 매개 변수)와 위의 생산자 기능 p 만있는 채널을 만듭니다.

이런 식으로 채널이 생성되면 p는 태스크로 캡슐화되고 시작되며 태스크가 완료되면 채널이 자동으로 닫힙니다. 즉, 생산, 배치, 폐쇄의 전체 과정이 단순화되고 상품이 배치되는 시장이 소비자에게 직접 제공됩니다. 예 :

julia> function producer(c::Channel)
       put!(c,"start")
       for n=1:4
           put!(c,n)
       end
       put!(c,"stop")
       end;

julia> for x in Channel(producer)
           println(x)
       end
start
1
2
3
4
stop

put!()채널이 생산자 내부의 모든 풋 작업을 자동으로 감지하고 적절한 버퍼를 분할하고 모든 풋이 완료된 후 자동으로 닫히기 때문에 생산자에 차단 없음을 알 수 있습니다 . 이러한 라이프 사이클 연관 메커니즘을 "바인딩"이라고합니다. bind(channelname,taskname)생성 된 채널과 태스크를 바인딩 할 수도 있습니다 . 이때 채널의 크기는 얼마든지 가능하며 (바인딩시 자동으로 조정 됨) 태스크 기능은 여전히 ​​채널 유형의 가상 매개 변수를 가지고 있어야합니다. 그러나 Task가 표현식을 캡슐화하는 경우 따옴표로 묶입니다. 즉 put!(c,元素), c=channelname. 예 :

julia> c0 = Channel(0)
Channel{Any}(sz_max:0,sz_curr:0)

julia> task = @async foreach(i->put!(c0,i),1:4)
Task (runnable) @0x0000000007cbd150

julia> bind(c0,task);

julia> for i in c0
       @show i
       end;
i = 1
i = 2
i = 3
i = 4

위의 c0 수정은 오류를보고합니다.

특히 bind()여러 제작자를 하나의 채널에 바인딩 하는 반복적으로 사용할 수 있습니다 .

추천

출처blog.csdn.net/iamzhtr/article/details/91348595