동시 프로그래밍의 목적은 프로그램을 더 빨리 실행하는 것이지만 동시성을 사용한다고해서 반드시 프로그램을 더 빨리 실행하는 것은 아닙니다. 동시 프로그래밍의 장점은 동시 프로그램의 수가 특정 순서에 도달 할 때만 반영 될 수 있습니다. 따라서 동시성이 높은 경우 동시 프로그래밍에 대해 이야기하는 것은 단지 의미가 있습니다. 동시성 부피가 높은 프로그램은 아직 개발되지 않았지만 동시성 학습은 일부 분산 아키텍처를 더 잘 이해하는 것입니다. 그런 다음 단일 스레드 프로그램과 같이 프로그램의 동시성 볼륨이 높지 않으면 단일 스레드의 실행 효율이 다중 스레드 프로그램의 실행 효율보다 높습니다. 이게 왜? 운영 체제에 익숙한 사람들은 CPU가 각 스레드에 시간 슬라이스를 할당하여 멀티 스레딩을 구현한다는 것을 알아야합니다. 이러한 방식으로 CPU가 한 작업에서 다른 작업으로 전환하면 이전 작업의 상태가 저장됩니다. 작업이 실행되면 CPU는 이전 작업의 상태를 계속 실행합니다. 이 과정을 컨텍스트 전환이라고합니다.
Java Multithreading에서 휘발성 키워드의 동기화 된 키워드가 중요한 역할을합니다. 모두 스레드 동기화를 구현할 수 있지만 하단에서 어떻게 구현됩니까?
휘발성 물질
휘발성은 각 스레드에 대한 변수의 가시성 만 보장 할 수 있지만 원자력을 보장 할 수는 없습니다. Java 언어 휘발성을 사용하는 방법에 대해별로 말하지 않을 것입니다. 제 제안은 java.util.concurrent.atomic 패키지의 클래스 라이브러리를 제외한 다른 상황에서 사용하는 것입니다. 자세한 설명은이 기사를 참조하십시오.
소개
다음 코드를 참조하십시오
패키지 org.go; public class go {휘발성 int i = 0; 개인 void inc () {i ++; } public static void main (String [] args) {go go = new go (); for (int i = 0; i <10; i ++) {새 스레드 (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (ride.ActiveCount ()> 1) {Thread.yield (); } system.out.println (go.i); }} 위의 코드의 각 실행 결과는 다르고 출력 번호는 항상 10000보다 적습니다. 이는 Inc ()를 수행 할 때 원자가 작동이 아니기 때문입니다. 아마도 일부 사람들은 동기화 된 Inc ()를 동기화하거나 패키지의 잠금 장치를 사용하여 스레드 동기화를 제어하기 위해 록스의 잠금을 사용하는 것이 좋습니다. 그러나 그들은 다음 솔루션만큼 좋지 않습니다.
패키지 org.go; import java.util.concurrent.atomic.atomicinteger; public class go {atomicinteger i = new atomicinteger (0); private void inc () {i.getAndIncrement (); } public static void main (String [] args) {go go = new go (); for (int i = 0; i <10; i ++) {새 스레드 (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (ride.ActiveCount ()> 1) {Thread.yield (); } system.out.println (go.i); }} 현재 원자의 구현을 이해하지 못하면 기본 원자 인테거가 잠금을 사용하여 구현 될 수 있다고 의심하므로 효율적이지 않을 수 있습니다. 정확히 무엇인가, 살펴 보겠습니다.
원자 클래스의 내부 구현
AtomicInteger이든 동시에 동시에 린드 넥타이 노드 클래스 ConcurrentLinkedqueue.node이든 정적 변수가 있습니다.
개인 정적 최종 Sun.misc.unsafe 불안한;이 클래스는 원자 시맨틱을 구현하는 Sun :: Misc :: 안전하지 않은 Java 캡슐화입니다. 기본 구현을보고 싶습니다. 나는 GCC4.8의 소스 코드를 가지고 있습니다. 로컬 경로와 비교할 때 Github로가는 경로를 찾는 것이 매우 편리합니다. 여기 봐.
인터페이스 getAndIncrement ()의 구현 예를 가져옵니다.
atomicinteger.java
개인 정적 최종 최종 안전하지 않은 안전하지 않은 = 불안하지 않은 경우;); public final int getAndincrement () {for (;;) {int current = get (); int next = current + 1; if (compareAndset (current, next))을 반환합니다. }} public final boolean compareandset (int reposs, int update) {return unsafe.compareandswapint (valueOffset, expling, update); } 루프에 대해서는주의를 기울이면 비교가 성공한 경우에만 반환됩니다. 그렇지 않으면 항상 비교합니다.
CompareAndset 구현이 호출됩니다. 여기서는 Oracle JDK의 구현이 약간 다르다는 것을 알았습니다. JDK에서 SRC를 보면 Oracle JDK가 안전하지 않은 getAndincrement ()를 호출한다는 것을 알 수 있지만, Oracle JDK가 불안한.java를 구현할 때 비교에서 만 호출해야한다고 생각합니다.
안전하지 않습니다. 자바
Public Native Boolean CompareAndswapint (Object OBJ, Long Offset, int expect, int update);
JNI를 통해 호출되는 C ++ 구현.
Natunsafe.cc
jbooleansun :: misc :: misc :: unsafe :: compareandswapint (jobject obj, jlong offset, jint expect, jint update) {jint *addr = (jint *) ((char *) obj + 오프셋); return CompareAndswap (addr, reposs, update);} 정적 인라인 boolcompareandswap (휘발성 jint *addr, jint old, jint new_val) {jboolean result = false; 스핀 락 잠금; if ((결과 = ( *addr == Old))) *addr = new_val; 반환 결과;} 불안정 :: CompareAndswapint는 정적 함수 compareandswap을 호출합니다. CompareAndswap은 스피 록을 잠금으로 사용합니다. 여기의 스핀 락은 Lockguard의 의미를 가지고 있으며, 이는 건설 중에 잠겨 있으며 파괴 중에 방출됩니다.
우리는 Spinlock에 집중해야합니다. 다음은 Spinlock이 릴리스되기 전에 원자 작업의 진정한 구현인지 확인하는 것입니다.
spinlock이란 무엇입니까?
Spinlock, 자원의 자물쇠를 얻기 위해 대기하는 바쁜 일종의 바쁘다. MUTEX의 현재 스레드 차단 및 필요한 자원을 기다리기 위해 CPU 리소스를 출시하는 것과 달리 Spinlock은 현탁 과정에 들어가지 않고 조건이 충족되기를 기다리고 CPU를 다시 경쟁하지 않습니다. 이는 스피 록이 자물쇠 대기 비용이 스레드 실행 컨텍스트 스위치보다 적은 경우에만 MUTEX보다 낫다는 것을 의미합니다.
Natunsafe.cc
클래스 spinlock {정적 휘발성 obj_addr_t lock; public : spinlock () {while (! compare_and_swap (& lock, 0, 1)) _jv_threadyield (); } ~ spinlock () {release_set (& lock, 0); }}; 정적 변수 정적 휘발성 OBJ_ADDR_T 잠금을 사용하십시오. 플래그 비트로, 가드는 C ++ RAII를 통해 구현되므로 소위 잠금은 실제로 정적 멤버 변수 OBJ_ADDR_T 잠금입니다. C ++의 휘발성은 동기화를 보장 할 수 없습니다. 보장되는 것은 생성자에서 호출 된 compare_and_swap과 정적 변수 잠금입니다. 이 잠금 변수가 1이면 기다려야합니다. 0 인 경우 원자 작동을 통해 1으로 설정하여 잠금을 얻었음을 나타냅니다.
여기서 정적 변수를 사용하는 것은 실제로 사고입니다. 즉, 모든 잠금없는 구조는 동일한 변수 (실제로 size_t)를 공유하여 잠금을 추가할지 여부를 구별합니다. 이 변수가 1으로 설정되면 다른 스핀 록을 기다려야합니다. Sun :: Misc :: 불안한 상태에서 개인 변수 휘발성 OBJ_ADDR_T 잠금을 추가하고 생성자 매개 변수로 Spinlock에 전달하는 이유는 무엇입니까? 이는 각 안전하지 않은 것에 대해 플래그 비트를 공유하는 것과 같습니다. 효과가 더 좋을까요?
_jv_threadyield 다음 파일에서 CPU 리소스는 시스템 호출 sched_yield (man 2 sched_yield)를 통해 포기됩니다. Macro HAS_SCHED_YILD는 구성에 정의되어 있습니다. 이는 컴파일 중에 정의가 정의되지 않으면 Spinlock을 실제 스핀 잠금이라고합니다.
posix-shreads.h
인라인 void_jv_threadield (void) {##ifdef have_sched_yield sched_yield ();#endif / * had_sched_yield * /} 이 lock.h는 다른 플랫폼마다 다른 구현이 있습니다. 우리는 IA64 (Intel AMD X64) 플랫폼을 예로 들어갑니다. 다른 구현은 여기에서 볼 수 있습니다.
IA64/locks.h
typedef size_t obj_addr_t; 인라인 정적 boolcompare_and_swap (volatile obj_addr_t *addr, obj_addr_t old, obj_addr_t new_val) {return __sync_bool_compare_and_swap (addr, old, new_val); obj_addr_t *addr, obj_addr_t new_val) {__asm__ __volatile __ ( ": : : :"memory "); *(addr) = new_val;}__sync_bool_compare_and__swap은 내장 GCC 기능이며 어셈블리 명령 "메모리"는 메모리 배리어를 완성합니다.
요컨대, 하드웨어는 멀티 코어 CPU 동기화를 보장하며 안전하지 않은 구현은 가능한 한 효율적입니다. GCC-Java는 상당히 효율적이며 Oracle과 OpenJDK는 더 나쁘지 않을 것이라고 믿습니다.
원자 운영 및 GCC 내장 원자 운영
원자 작동
Java 표현식 및 C ++ 표현식은 원자 연산이 아니므로 코드에 있다는 것을 의미합니다.
// 내가 스레드간에 공유되는 변수 i ++라고 가정합니다.
멀티 스레드 환경에서, 나는 액세스하는 것이 원자력이 아니며 실제로 다음 3 개의 피연산자로 나뉩니다.
컴파일러는 실행시기를 변경하므로 실행 결과는 예상되지 않을 수 있습니다.
GCC 내장 원자 작동
GCC에는 원자 운영이 내장되어 있으며 4.1.2에서 추가되었습니다. 이전에는 인라인 어셈블리를 사용하여 구현되었습니다.
유형 __sync_fetch_and_add (유형 *ptr, 유형 값, ...) 유형 __sync_fetch_and_sub 유형 __sync_fetch_and_sub (type *ptr, type value, ...) type __sync_fetch_and_or (type *ptr, type value,… 값, ...) 유형 __sync_fetch_and_xor (유형 *ptr, 유형 값, ...) 유형 __sync_fetch_and_nand (type *ptr, type value, ...) type __sync_add_and_fetch (유형 *ptr, 유형 값, ...) 유형 __sync_sub_and_fetch (type *ptr, ...) 유형, ...). (type *ptr, type value, ...) 유형 __sync_and_and_fetch (type *ptr, type value, ...) type __sync_and_and_fetch (유형 *ptr, 유형 값, ...) 유형 __sync_xor_and_fetch (type *ptr, type value, ...) 유형 __sync_nand_and_fetch (type *ptr, ...) bool. __sync_bool_compare_and_swap (유형 *ptr, type oldval type newval, ...) 유형 oldval type newval, ...) 유형 __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...) __ sync_synchronize (...) 유형 __sync_lock_test_and_set (type *ptr, ptr, ptr, ...). __sync_lock_release (type *ptr, ...)
주목해야 할 것은 다음과 같습니다.
OpenJDK 관련 파일
다음은 Github에서 OpenJDK9의 일부 원자 작동 구현으로 알아야 할 사람들을 도와주기를 희망합니다. 결국 OpenJDK는 GCC보다 널리 사용됩니다. - 그러나 결국 Oracle JDK에 대한 소스 코드는 없지만 OpenJDK와 Oracle 사이의 소스 코드는 매우 작습니다.
atomicinteger.java
insafe.java::compareandexChangeObject
unsafe.cpp :: unsafe_compareandexchangeobject
ooop.inline.hpp :: ooopdesc :: atomic_compare_exchange_oop
atomic_linux_x86.hpp :: atomic :: cmpxchg
인라인 jlong atomic :: cmpxchg (jlong exchange_value, 휘발성 jlong* dest, jlong comprale_value, cmmpxchg_memory_order 순서) {bool mp = os :: is_mp (); __asm__ __volatile__ (leop_if_mp (%4) "cmpxchgq%1, (%3)": "= a"(exchange_value) : "r"(exchange_value), "a"(compare_value), "r"(dest), "r"(mp) : "cc"); return exchange_value;} 여기서는 C/C ++에 익숙하지 않은 Java 프로그래머에게 프롬프트를 제공해야합니다. 임베디드 어셈블리 지침의 형식은 다음과 같습니다
__asm__ [__volatile __] (어셈블리 템플릿 // 어셈블리 템플릿 : [출력 피연산자 목록] // 입력 목록 : [입력 피연산자 목록] // 출력 목록 : [Clobber List]) // 목록 파괴
어셈블리 템플릿의 %1, %3, %4는 다음 매개 변수 목록 { "r"(exchange_value), "r"(dest), "r"(mp)}에 해당하며 매개 변수 목록은 쉼표로 분리되고 0에서 분류됩니다. 출력 매개 변수는 첫 번째 콜론의 오른쪽에 놓여지고 출력 매개 변수는 두 번째 콜론의 오른쪽에 놓여 있습니다. "r"은 일반 레지스터에 넣는 것을 의미합니다. CMPXCHG 명령어는 EAX 레지스터의 사용, 즉 매개 변수 %2를 의미합니다.
다른 세부 사항은 여기에 나열되지 않습니다. GCC의 구현은 교환하기 위해 포인터를 전달하는 것이며, 성공적인 비교 후에는 값이 직접 할당되며 (원자력을 지정) 원자력은 Spinlock에 의해 보장됩니다.
OpenJDK의 구현은 교환하기 위해 포인터를 전달하고 어셈블리 명령 CMMPXCHGQ를 통해 값을 직접 할당하는 것이며, 원자력은 어셈블리 명령을 통해 보장됩니다. 물론, GCC의 스핀 락의 기본 층은 CMMPXCHGQ를 통해 보장됩니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.