생산자 스레드가 완료되기 전에 소비자 스레드가 중지되는 이유는 무엇입니까?

하 카투

최근에 제한된 잠금 해제 대기열을 작성하고 몇 가지 테스트를 진행했습니다. 테스트에서 일부 스레드는 소수에서 시작하여 생산자 스레드 쌍 수의 6 배까지 세고 결정 론적 Miller Rabin 테스트를 사용하여 모든 숫자를 확인하고 소수를 대기열에 삽입하여 소수와 일부 스레드를 생성합니다. (대기열에서 요소를 제거하고 소수인지 확인하여) 소수를 사용합니다. 생산자 스레드는 1 mod 6과 동일한 소수를 생성하는 각 쌍에서 하나와 쌍으로 제공되고 다른 하나는 5 mod 6과 동일한 소수를 생성합니다 (0, 2, 3 또는 4 mod 6과 동일한 모든 숫자는 2 및 3) 주 스레드는 2와 3을 생성합니다. 생성되지 않은 스레드 수에 대한 글로벌 카운터가 있습니다. 생산자 스레드 또는 메인이 소수 생성을 완료 할 때마다이 카운터는 원자 적으로 감소합니다. 소비자 스레드는 0이 아닌 동안 반복됩니다.

소수가 실제로 큐를 통과하는지 여부를 결정하기 위해 모든 스레드에서 생성되고 소비되는 소수의 0 ~ 3 번째 모멘트를 계산하고 생산자 스레드의 모멘트 합이 모멘트 합과 같은지 확인합니다. 소비자 스레드. n 번째 순간은 n 번째 거듭 제곱의 합일뿐입니다. 따라서 이것은 소수의 수, 합, 제곱의 합, 큐브의 합이 모두 일치 함을 의미합니다. 시퀀스가 서로의 순열이면 모든 순간이 일치하므로 길이 n의 시퀀스가 ​​실제로 순열인지 확인하기 위해 처음 n을 확인해야하지만 처음 4 개의 일치는 시퀀스가 ​​일치하지 않을 가능성이 매우 적다는 것을 의미합니다.

내 잠금 해제 대기열은 실제로 작동하지만 어떤 이유로 대기열에 여전히 요소가있는 동안 소비자 스레드가 모두 중지됩니다. 나는 생산자 스레드가 모든 프라임을 대기열에 삽입 한 후에 만 ​​생산 카운터를 감소시키고 모든 생산 스레드가 감소한 후에 만 ​​생산 카운터가 0과 같을 수 있기 때문에 이유를 이해할 수 없습니다. 따라서 생성 카운터가 0 일 때마다 모든 요소가 큐에 삽입되었습니다. 그러나 소비자가 요소를 제거하려고 시도하면 제거가 실패하므로 queue.full (대기열의 요소 수)이 0 일 때만 제거가 실패합니다. 따라서 생성 카운터가 0이면 소비자는 다음까지 성공적으로 소비 할 수 있어야합니다. queue.full은 0이며 생성 카운터를 확인하지 않고 대기열이 소진 될 때까지 반환해서는 안됩니다.

그러나 생성 카운터 외에도 check queue.full 제거 주위에 while 루프를 만들면 소비자가 일찍 돌아 오지 않습니다. 즉, 내가 변할 때

__atomic_load_n(&producing, __ATOMIC_SEQ_CST)

...에

__atomic_load_n(&producing, __ATOMIC_SEQ_CST) || __atomic_load_n(&Q.full, __ATOMIC_SEQ_CST)

그냥 작동합니다. 내 코드는 속성, __atomic builtins, __auto_type, 문 표현식, 128 비트 정수, __builtin_ctzll 및 '\ e'와 같은 적절한 양의 gcc 확장, 지정된 이니셜 라이저 및 복합 리터럴과 같은 C99 기능, pthread를 사용합니다. 나는 또한 약한 버전이 작동하더라도 순차적으로 일관된 메모리 순서와 강력한 비교 및 ​​스왑을 사용하고 있습니다. 왜냐하면 문제가 발생하는 동안 문제가 발생하지 않기 때문입니다. 다음은 헤더 queue.h입니다.

#ifndef __QUEUE_H__
#define __QUEUE_H__

#include <stddef.h>
#include <inttypes.h>

typedef struct __attribute__((__designated_init__)){//using positional initializers for a struct is terrible
    void *buf;
    uint8_t *flags;//insert started, insert complete, remove started
    size_t cap, full;
    uint64_t a, b;
} queue_t;

typedef struct __attribute__((__designated_init__)){
    size_t size;
} queue_ft;//this struct serves as a class for queue objects: any data specific to the object goes in the queue_t struct and any shared data goes here

int queue_insert(queue_t*, const queue_ft*, void *elem);

int queue_remove(queue_t*, const queue_ft*, void *out);

int queue_init(queue_t*, const queue_ft*, size_t reserve);

void queue_destroy(queue_t*, const queue_ft*);

#endif

다음은 라이브러리 소스 queue.c입니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "queue.h"

int queue_insert(queue_t *self, const queue_ft *ft, void *elem){
    uint64_t i;
    while(1){
        uint8_t flag = 0;
        if(__atomic_load_n(&self->full, __ATOMIC_SEQ_CST) == self->cap){
            return 0;
        }
        i = __atomic_load_n(&self->b, __ATOMIC_SEQ_CST);
        if(__atomic_compare_exchange_n(self->flags + i, &flag, 0x80, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)){//set the insert started flag if all flags are clear
            break;
        }
    }
    __atomic_fetch_add(&self->full, 1, __ATOMIC_SEQ_CST);
    uint64_t b = i;
    while(!__atomic_compare_exchange_n(&self->b, &b, (b + 1)%self->cap, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));//increase the b endpoint of the queue with wraparaound
    memcpy(self->buf + i*ft->size, elem, ft->size);//actually insert the item.  accesses to the buffer mirror accesses to the flags so this is safe
    __atomic_thread_fence(memory_order_seq_cst);
    __atomic_store_n(self->flags + i, 0xc0, __ATOMIC_SEQ_CST);//set the insert completed flag
    return 1;
}

int queue_remove(queue_t *self, const queue_ft *ft, void *out){
    uint64_t i;
    while(1){
        uint8_t flag = 0xc0;
        if(!__atomic_load_n(&self->full, __ATOMIC_SEQ_CST)){
            return 0;
        }
        i = __atomic_load_n(&self->a, __ATOMIC_SEQ_CST);
        if(__atomic_compare_exchange_n(self->flags + i, &flag, 0xe0, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)){//set the remove started flag if insert started and insert completed are set but the other flags are clear
            break;
        }
    }
    __atomic_fetch_sub(&self->full, 1, __ATOMIC_SEQ_CST);
    uint64_t a = i;
    while(!__atomic_compare_exchange_n(&self->a, &a, (a + 1)%self->cap, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));//increase the a endpoint of the queue with wraparound
    memcpy(out, self->buf + i*ft->size, ft->size);//actually remove the item.
    __atomic_thread_fence(__ATOMIC_SEQ_CST);
    __atomic_store_n(self->flags + i, 0x00, __ATOMIC_SEQ_CST);//clear all the flags to mark the remove as completed
    return 1;
}

int queue_init(queue_t *self, const queue_ft *ft, size_t reserve){
    void *buf = malloc(reserve*ft->size);
    if(!buf){
        return 0;
    }
    uint8_t *flags = calloc(reserve, sizeof(uint8_t));
    if(!flags){
        free(buf);
        return 0;
    }
    *self = (queue_t){
        .buf=buf,
        .flags=flags,
        .cap=reserve,
        .full=0,
        .a=0,.b=0
    };
    return 1;
}

void queue_destroy(queue_t *self, const queue_ft *ft){
    free(self->buf);
    free(self->flags);
}

다음은 테스트 프로그램 소스 test_queue_pc.c입니다.

#define _POSIX_C_SOURCE 201612UL

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <math.h>
#include <time.h>
#include "queue.h"

//Generate primes up to this number.  Note 78498 is the number of primes below 1000000; this is hard coded because the queue does not support growing yet.
#define MAX 1000000
#define QUEUE_SIZE 78498
#define NUM_PRODUCER_PAIRS 3
#define NUM_CONSUMERS 2
//Every producer and consumer thread calculates the 0th through 3rd moments of the sequence of primes it sees, as well as testing them for primality.
//The nth moment is the sum of the nth powers, thus, the order does not matter and if the primes are the same in both the producers and the consumers
//then the sums of the moments will also be the same.  I check that the 0th through 3rd moments match which means it is nearly certain the primes go through
//the queue.
#define NUM_MOMENTS 4

//Deterministic Miller Rabin witnesses (see https://en.wikipedia.org/wiki/Miller–Rabin_primality_test)
#define DMR_PRIMES (uint64_t[]){2, 13, 23, 1662803}
#define DMR_PRIMES_C 4

//Macro to split an integer into three parts.  The first part has the 2**0, 2**3, 2**6, ..., 2**60 bits of the original and 0 elsewhere.
//The second part has the 2**1, 2**4, 2**7, ..., 2**61 bits of the original and 0 elsewhere.  The last part has the 2**2, ..., 2**62 bits.
//The 2**63 bit is lost.  The masks represent the sums of geometric sequences.  The original number can be obtained by bitwise or or xor on the parts.
//I spread the uint64_t's (which are unsigned long longs) over 3 uint64_t's so that they take up 24 bytes and memcpy'ing them happens in multiple steps.
//This spreading is only done on primes that have been produced before they are put into the queue.  The consumers then recombine and verify them.
#define SPREAD_EMPLACE(n) ({__auto_type _n = (n); &(spread_integer){(_n)&(((1ULL<<60)-1)/7), (_n)&(((1ULL<<61)-2)/7), (_n)&(((1ULL<<62)-4)/7)};})

typedef struct{
    uint64_t x, y, z;
} spread_integer;

queue_ft spread_integer_ft = {.size= sizeof(spread_integer)};

queue_t Q;
//Start producing count at 1 + (NUM_PRODUCING_THREADS << 1) because main generates 2 and 3 and reduce it by 1 every time a producer thread finishes
int producing = 1 + (NUM_PRODUCER_PAIRS << 1);

//Uses the binary algorithm for modular exponentiation (https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
//It is a helper function for isPrime
uint64_t powmod(unsigned __int128 b, uint64_t e, uint64_t n){
    unsigned __int128 r = 1;
    b %= n;
    while(e){
        if(e&1){
            r = r*b%n;
        }
        e >>= 1;
        b = b*b%n;
    }
    return (uint64_t)r;
}

//uses deterministic Miller Rabin primality test
int isPrime(uint64_t n){
    uint64_t s, d;//s, d | 2^s*d = n - 1
    if(n%2 == 0){
        return n == 2;
    }
    --n;
    s = __builtin_ctzll(n);
    d = n>>s;
    ++n;
    for(uint64_t i = 0, a, x; i < DMR_PRIMES_C; ++i){
        a = DMR_PRIMES[i];
        if(a >= n){
            break;
        }
        x = powmod(a, d, n);
        if(x == 1 || x == n - 1){
            goto CONTINUE_WITNESSLOOP;
        }
        for(a = 0; a < s - 1; ++a){
            x = powmod(x, 2, n);
            if(x == 1){
                return 0;
            }
            if(x == n - 1){
                goto CONTINUE_WITNESSLOOP;
            }
        }
        return 0;
        CONTINUE_WITNESSLOOP:;
    }
    return 1;
}

void *produce(void *_moments){
    uint64_t *moments = _moments, n = *moments;//the output argument for the 0th moment serves as the input argument for the number to start checking for primes at
    *moments = 0;
    for(; n < MAX; n += 6*NUM_PRODUCER_PAIRS){//the producers are paired so one of every pair generates primes equal to 1 mod 6 and the other equal to 5 mod 6.  main generates 2 and 3 the only exceptions
        if(isPrime(n)){
            for(uint64_t m = 1, i = 0; i < NUM_MOMENTS; m *= n, ++i){
                moments[i] += m;
            }
            if(!queue_insert(&Q, &spread_integer_ft, SPREAD_EMPLACE(n))){
                fprintf(stderr, "\e[1;31mERROR: Could not insert into queue.\e[0m\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    __atomic_fetch_sub(&producing, 1, __ATOMIC_SEQ_CST);//this thread is done generating primes; reduce producing counter by 1
    return moments;
}

void *consume(void *_moments){
    uint64_t *moments = _moments;
    while(__atomic_load_n(&producing, __ATOMIC_SEQ_CST) || __atomic_load_n(&Q.full, __ATOMIC_SEQ_CST)){//busy loop while some threads are producing
        spread_integer xyz;
        if(queue_remove(&Q, &spread_integer_ft, &xyz)){
            uint64_t n = xyz.x | xyz.y | xyz.z;
            if(isPrime(n)){
                for(uint64_t m = 1, i = 0; i < NUM_MOMENTS; m *= n, ++i){
                    moments[i] += m;
                }
            }else{
                fprintf(stderr, "\e[1;31mERROR: Generated a prime that fails deterministic Miller Rabin.\e[0m\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    return moments;
}

int main(void){
    if(!queue_init(&Q, &spread_integer_ft, QUEUE_SIZE)){
        fprintf(stderr, "\e[1;31mERROR: Could not initialize queue.\e[0m\n");
        exit(EXIT_FAILURE);
    }
    pthread_t producers[NUM_PRODUCER_PAIRS << 1], consumers[NUM_CONSUMERS];
    uint64_t moments[(NUM_PRODUCER_PAIRS << 1) + 1 + NUM_CONSUMERS + 1][NUM_MOMENTS] = {};//the 2 extras are because main produces the primes 2 and 3 and consumes primes the consumers leave behind
    for(size_t i = 0; i < NUM_CONSUMERS; ++i){//create consumers first to increase likelihood of causing bugs
        if(pthread_create(consumers + i, NULL, consume, moments[(NUM_PRODUCER_PAIRS << 1) + 1 + i])){
            fprintf(stderr, "\e[1;31mERROR: Could not create consumer thread.\e[0m\n");
            exit(EXIT_FAILURE);
        }
    }
    for(size_t i = 0; i < NUM_PRODUCER_PAIRS; ++i){
        moments[i << 1][0] = 5 + 6*i;
        if(pthread_create(producers + (i << 1), NULL, produce, moments[i << 1])){
            fprintf(stderr, "\e[1;31mERROR: Could not create producer thread.\e[0m\n");
            exit(EXIT_FAILURE);
        }
        moments[(i << 1) + 1][0] = 7 + 6*i;
        if(pthread_create(producers + (i << 1) + 1, NULL, produce, moments[(i << 1) + 1])){
            fprintf(stderr, "\e[1;31mERROR: Could not create producer thread.\e[0m\n");
            exit(EXIT_FAILURE);
        }
    }
    for(uint64_t n = 2; n < 4; ++n){
        for(uint64_t m = 1, i = 0; i < NUM_MOMENTS; m *= n, ++i){
            moments[NUM_PRODUCER_PAIRS << 1][i] += m;
        }
        if(!queue_insert(&Q, &spread_integer_ft, SPREAD_EMPLACE(n))){
            fprintf(stderr, "\e[1;31mERROR: Could not insert into queue.\e[0m\n");
            exit(EXIT_FAILURE);
        }
    }
    __atomic_fetch_sub(&producing, 1, __ATOMIC_SEQ_CST);
    uint64_t c = 0;
    for(size_t i = 0; i < NUM_CONSUMERS; ++i){//join consumers first to bait bugs.  Note consumers should not finish until the producing counter reaches 0
        void *_c;
        if(pthread_join(consumers[i], &_c)){
            fprintf(stderr, "\e[1;31mERROR: Could not join consumer thread.\e[0m\n");
            exit(EXIT_FAILURE);
        }
        c += (uintptr_t)_c;
    }
    for(size_t i = 0; i < NUM_PRODUCER_PAIRS << 1; ++i){
        if(pthread_join(producers[i], NULL)){
            fprintf(stderr, "\e[1;31mERROR: Could not join producer thread.\e[0m\n");
            exit(EXIT_FAILURE);
        }
    }
    //this really should not be happening because the consumer threads only return after the producing counter reaches 0,
    //which only happens after all of the producer threads are done inserting items into the queue.
    if(Q.full){
        fprintf(stdout, "\e[1;31mWTF: Q.full != 0\nproducing == %d\e[0m\n", producing);
    }
    while(Q.full){
        spread_integer xyz;
        if(!queue_remove(&Q, &spread_integer_ft, &xyz)){
            fprintf(stderr, "\e[1;31mERROR: Could not remove from non empty queue.\e[0m\n");
            exit(EXIT_FAILURE);
        }
        uint64_t n = xyz.x | xyz.y | xyz.z;
        if(isPrime(n)){
            for(uint64_t m = 1, i = 0; i < NUM_MOMENTS; m *= n, ++i){
                moments[(NUM_PRODUCER_PAIRS << 1) + 1 + NUM_CONSUMERS][i] += m;
            }
        }else{
            fprintf(stderr, "\e[1;31mERROR: Generated a prime that fails deterministic Miller Rabin.\e[0m\n");
            exit(EXIT_FAILURE);
        }
    }
    queue_destroy(&Q, &spread_integer_ft);
    for(uint64_t i = 0, p, c, j; i < NUM_MOMENTS; ++i){
        for(j = p = 0; j < (NUM_PRODUCER_PAIRS << 1) + 1; ++j){
            p += moments[j][i];
        }
        for(c = 0; j < (NUM_PRODUCER_PAIRS << 1) + 1 + NUM_CONSUMERS + 1; ++j){
            c += moments[j][i];
        }
        printf("Moment %"PRIu64" %"PRIu64" -> %"PRIu64"\n", i, p, c);
    }
}

그런 다음 컴파일

gcc -o test_queue_pc queue.c test_queue_pc.c -Wall -std=c99 -g -O0 -pthread -fuse-ld=gold -flto -lm

소비자 스레드가 생산자가 완료되기를 기다리더라도 대기열이 비어 있기 전에 반환되는 이유는 바로 on producing이지만 반복 할 때 올바른 작업을 수행하는 이유는 producing || Q.full무엇입니까?

명목상 동물

소비자 스레드는 생산자가 완료 될 때까지 기다리더라도 대기열이 비기 전에 반환되는 이유는 생산 중에 만 반복되지만 생산 중에는 반복 될 때 올바른 작업을 수행합니다 || Q.full?

더 이상 생산자가 없다는 것은 새로운 항목이 대기열에 추가되지 않음을 의미하기 때문입니다. 큐가 이미 비어 있음을 의미 하지는 않습니다 .

생산자가 소비자보다 빠른 경우를 고려하십시오. 그들은 자신의 물건을 대기열에 추가하고 종료합니다. 이 시점에서 대기열에 항목이 있지만 활성 생산자 수는 0입니다. 소비자가 활성 생산자가 있는지 여부 만 확인하면 이미 대기열에있는 항목을 놓칠 수 있습니다.


확인하는 것이 중요합니다.

if ((active producers) || (items in queue))

여기 C99에서 올바른 것입니다. ( ||연산자는 왼쪽이 평가 된 후 시퀀스 포인트가 있습니다. 즉, 오른쪽이 왼쪽보다 먼저 평가되지 않습니다.)

활성 생산자 만 확인하면 생산자가 소비자보다 빠른 경우를 놓치고 대기열에 항목이있는 동안 종료됩니다.

대기열의 항목 만 확인하면 생산자가 여전히 대기열에 항목을 추가하는 경우를 놓치게됩니다.

먼저 대기열이 비어 있는지 확인하면 레이스 창이 열립니다. 소비자가 대기열이 비어 있는지 확인한 후 소비자가 활성 생산자가 있는지 확인하기 전에 생산자는 하나 이상의 항목을 대기열에 추가하고 종료 할 수 있습니다.

먼저 활성 생산자가 있는지 확인해야합니다. 활성 생산자가 있고 대기열이 지금 비어 있으면 소비자는 새 항목이 대기열에 도착할 때까지 기다려야합니다 (활성 생산자 수가 0으로 떨어지거나 새 항목이 대기열에 도착할 때까지). 생산자, 소비자는 대기열에 항목이 있는지 확인해야합니다. 활성 생산자가 없음은 대기열에 새 항목이 나타나지 않음을 의미하지만 대기열이 이미 비어 있음을 의미하지는 않습니다.

이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.

침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

분류에서Dev

소스 Observable이 완료되기 전에`merge` 연산자가 완료되는 이유는 무엇입니까?

분류에서Dev

이 스레드가 자체적으로 완료되지 않는 이유는 무엇입니까?

분류에서Dev

스레드는 생산자-소비자 스레드 조건에 따라 종료되지 않습니다.

분류에서Dev

Java-for 루프의 스레드는 이전 스레드가 완료되기 전에 생성되지 않습니다.

분류에서Dev

ThreadPoolExecutor는 마지막 스레드가 완료되기 전에 새 스레드를 시작하지 않습니다.

분류에서Dev

작업이 완료되기 전에 스레드가 종료되는 것을 방지하는 방법은 무엇입니까?

분류에서Dev

내 (효소 시뮬레이션 이벤트) 동기 이벤트 처리기 전에 테스트가 완료되는 이유는 무엇입니까?

분류에서Dev

주 스레드 이전에 다른 모든 스레드가 완료 되었는데도 .join이 여전히 필요한 이유는 무엇입니까?

분류에서Dev

부모 스레드가 서블릿 컨테이너에서도 종료되기 전에 자식 스레드가 종료되기를 기다 립니까?

분류에서Dev

부모 스레드가 서블릿 컨테이너에서도 종료되기 전에 자식 스레드가 종료되기를 기다 립니까?

분류에서Dev

팔레트에서 드래그 앤 드롭 할 때 필드가 디자인 인터페이스에 표시되지 않는 이유는 무엇입니까?

분류에서Dev

내 Android 프로젝트 레이아웃 중 하나에 만 앱 네임 스페이스가 자동 완성되지 않는 이유는 무엇입니까?

분류에서Dev

Java 프로그램에 갑자기 3 개의 스레드가있는 이유는 무엇입니까?

분류에서Dev

관찰 가능한 객체가 새 스레드로 실행될 때 관찰자에게 알림이 표시되지 않는 이유는 무엇입니까?

분류에서Dev

취소가 호출 된 후에도 스레드가 계속 실행되는 이유는 무엇입니까?

분류에서Dev

이 작업이 완료되기 전에 코드가 실행되는 이유는 무엇입니까?

분류에서Dev

Google 스프레드 시트에서 소비자 물가 지수 (CPI)를 얻는 방법은 무엇입니까?

분류에서Dev

호출자 메서드의 다음 줄이 실행되기 전에 비동기 메서드가 종료되는 이유는 무엇입니까?

분류에서Dev

모든 스레드가 완료되기 전에 자바 메소드로부터 반환

분류에서Dev

요소가 완전히 스크롤되기 전에 내 onscroll 기능이 중지되는 이유는 무엇입니까?

분류에서Dev

생산자가 소비자 인 Python과 Trio, 작업이 완료되면 정상적으로 종료하는 방법은 무엇입니까?

분류에서Dev

Windows 10이 Windows 서비스에서 작업자 스레드를 종료 한 이유는 무엇입니까?

분류에서Dev

비동기 함수의 메인 스레드에서 완료를 호출하는 이유는 무엇입니까?

분류에서Dev

스레드가 실행 중임에도 불구하고 스레드의 변수 값이 일정하게 유지되는 이유는 무엇입니까?

분류에서Dev

이 예제에서 여러 스레드가 비효율적으로 분산되는 이유는 무엇입니까?

분류에서Dev

Java에서 비동기 생산자 / 소스를 만드는 방법은 무엇입니까?

분류에서Dev

Pandas 비교 연산자가 인덱스에 정렬되지 않는 이유는 무엇입니까?

분류에서Dev

메서드가 완료되기 전에 인스턴스 메서드에서 약한 멤버가 null이 될 수있는 이유는 무엇입니까?

분류에서Dev

숫자가 아닌 레코드가 정렬에서 "0"뒤에 표시되는 이유는 무엇입니까?

Related 관련 기사

  1. 1

    소스 Observable이 완료되기 전에`merge` 연산자가 완료되는 이유는 무엇입니까?

  2. 2

    이 스레드가 자체적으로 완료되지 않는 이유는 무엇입니까?

  3. 3

    스레드는 생산자-소비자 스레드 조건에 따라 종료되지 않습니다.

  4. 4

    Java-for 루프의 스레드는 이전 스레드가 완료되기 전에 생성되지 않습니다.

  5. 5

    ThreadPoolExecutor는 마지막 스레드가 완료되기 전에 새 스레드를 시작하지 않습니다.

  6. 6

    작업이 완료되기 전에 스레드가 종료되는 것을 방지하는 방법은 무엇입니까?

  7. 7

    내 (효소 시뮬레이션 이벤트) 동기 이벤트 처리기 전에 테스트가 완료되는 이유는 무엇입니까?

  8. 8

    주 스레드 이전에 다른 모든 스레드가 완료 되었는데도 .join이 여전히 필요한 이유는 무엇입니까?

  9. 9

    부모 스레드가 서블릿 컨테이너에서도 종료되기 전에 자식 스레드가 종료되기를 기다 립니까?

  10. 10

    부모 스레드가 서블릿 컨테이너에서도 종료되기 전에 자식 스레드가 종료되기를 기다 립니까?

  11. 11

    팔레트에서 드래그 앤 드롭 할 때 필드가 디자인 인터페이스에 표시되지 않는 이유는 무엇입니까?

  12. 12

    내 Android 프로젝트 레이아웃 중 하나에 만 앱 네임 스페이스가 자동 완성되지 않는 이유는 무엇입니까?

  13. 13

    Java 프로그램에 갑자기 3 개의 스레드가있는 이유는 무엇입니까?

  14. 14

    관찰 가능한 객체가 새 스레드로 실행될 때 관찰자에게 알림이 표시되지 않는 이유는 무엇입니까?

  15. 15

    취소가 호출 된 후에도 스레드가 계속 실행되는 이유는 무엇입니까?

  16. 16

    이 작업이 완료되기 전에 코드가 실행되는 이유는 무엇입니까?

  17. 17

    Google 스프레드 시트에서 소비자 물가 지수 (CPI)를 얻는 방법은 무엇입니까?

  18. 18

    호출자 메서드의 다음 줄이 실행되기 전에 비동기 메서드가 종료되는 이유는 무엇입니까?

  19. 19

    모든 스레드가 완료되기 전에 자바 메소드로부터 반환

  20. 20

    요소가 완전히 스크롤되기 전에 내 onscroll 기능이 중지되는 이유는 무엇입니까?

  21. 21

    생산자가 소비자 인 Python과 Trio, 작업이 완료되면 정상적으로 종료하는 방법은 무엇입니까?

  22. 22

    Windows 10이 Windows 서비스에서 작업자 스레드를 종료 한 이유는 무엇입니까?

  23. 23

    비동기 함수의 메인 스레드에서 완료를 호출하는 이유는 무엇입니까?

  24. 24

    스레드가 실행 중임에도 불구하고 스레드의 변수 값이 일정하게 유지되는 이유는 무엇입니까?

  25. 25

    이 예제에서 여러 스레드가 비효율적으로 분산되는 이유는 무엇입니까?

  26. 26

    Java에서 비동기 생산자 / 소스를 만드는 방법은 무엇입니까?

  27. 27

    Pandas 비교 연산자가 인덱스에 정렬되지 않는 이유는 무엇입니까?

  28. 28

    메서드가 완료되기 전에 인스턴스 메서드에서 약한 멤버가 null이 될 수있는 이유는 무엇입니까?

  29. 29

    숫자가 아닌 레코드가 정렬에서 "0"뒤에 표시되는 이유는 무엇입니까?

뜨겁다태그

보관