C\#동시 다발 실전 기록 의 Parallel.ForEach 사용

6848 단어 c#parallel.foreach
선언:
최근 고객 에 게 식비 계산 시스템 을 개발 해 2000 명의 식 사 를 계산 해 야 한다.수 요 는 직원 의 예약 신고 계획 에 따라 소비 기록 을 검사 하 는 것 이다.만약 에 신고 하지 않 은 음식 이 카드 를 사용 하거나 신고 하지 않 은 것 이 있 으 면 일정한 금액 공제 등 일련의 규칙 을 실시 해 야 한다.처음에는 제 생각 이 쉬 웠 습 니 다.바로 하나의 for 순환 으로 해 결 했 습 니 다.통계 결 과 는 괜 찮 았 지만 계산 이 너무 느 려 서 7,8 분 이 걸 렸 습 니 다.이런 시스템 서 비 스 는 시간 초과 보고 가 잘못 되 어 좀 불쾌 하 다.시간 도 많 지 않 아 먼저 사용자 에 게 제출 하여 사용 하 게 되 었 고,뒤의 논리 가 또 증가 하여 계산 시간 이 길 어 졌 으 며,전체 계산 을 한 번 하 는 데 10 분 가까이 걸 렸 다.이것 은 사용자 에 게 받 아들 일 수 있 는 것 입 니 다.
다 중 스 레 드 하면 가장 먼저 떠 오 르 는 것 은 Task 입 니 다.왜냐하면.net 4.0 이상 Task 는 좋 은 방법 을 많이 포 장 했 기 때 문 입 니 다.그러나 Task 는 스 레 드 를 많이 열 어 임 무 를 수행 하고 마지막 으로 결 과 를 통합 하면 빠 를 수 있 지만 더 빠 르 고 싶 어서 다른 대상 이 생각 났 습 니 다.Parallel.이전에 유지보수 코드 는 다른 사람 이 쓴 Parallel.Invoke 를 만난 적 이 있 습 니 다.이 함수 의 역할 은 여러 가지 임 무 를 동시에 수행 하 는 것 이 라 고 지 정 했 을 뿐 입 니 다.만약 에 여러 시간 이 걸 리 는 작업 을 만나면 그들 사이 에 변 수 를 기여 하지 않 는 방법 이 좋 습 니 다.제 상황 은 하나의 집합 을 동시에 실행 하려 고 하기 때문에 List.ForAll 이라는 방법 을 사 용 했 습 니 다.사실은 확장 방법 입 니 다.완전한 호출 은 List.As Parallel().ForAll 입 니 다.먼저 병행 을 지원 하 는 집합 으로 전환 해 야 합 니 다.Parallel.Foreach 와 같 습 니 다.집합 안의 요 소 를 동시에 수행 하 는 것 이 목적 입 니 다.
그래서 원래 의 foreach 를 List.As Parallel().ForAll 로 바 꾸 었 습 니 다.실행 하기 시 작 했 습 니 다.과연 속도 가 놀 라 웠 습 니 다.2 분 도 안 되 어 결 과 를 삽입 하 였 으 나 마지막 으로 메 인 키 가 중복 되 는 오류 입 니 다.이 오 류 는 병발 을 사 용 했 기 때문에 이 때 변 수 는 증가 하 는 것 입 니 다.사실은 여러 스 레 드 가 동시에 id 값 을 얻 었 기 때 문 입 니 다.모두 증가 한 후에 반복 되 었 습 니 다.예 를 들 어 다음 과 같 습 니 다.

int num = 1;
      List<int> list = new List<int>();
      for (int i = 1; i <= 2000; i++)
      {
        list.Add(i);
      }
      Console.WriteLine($"num    :" + num.ToString());
      list.AsParallel().ForAll(n =>
      {
        num++;
      });
      Console.WriteLine($"   ,  {list.Count}   :" + num.ToString());
      Console.ReadKey();
이 코드 는 하나의 변 수 를 2000 번 증가 시 키 는 것 으로 정상 적 인 결 과 는 2001 이 어야 하지만 실제 결 과 는 다음 과 같다.

경험 이 있 는 학생 들 은 바로 자 물 쇠 를 추가 해 야 한 다 는 것 을 생각 할 수 있 습 니 다.C\#많은 자물쇠 대상 이 내장 되 어 있 습 니 다.예 를 들 어 lock 상호 배척 자물쇠,Interlocked 내부 자물쇠,Monitor 등 몇 가지 흔히 볼 수 있 습 니 다.lock 내부 실현 은 사실은 Monitor 대상 을 사용 한 것 입 니 다.변수 자체 증가,Interlocked 대상 에 게'변수 자체 증가,자체 감소,또는 추가 등 방법'을 제공 합 니 다.저 희 는 자체 증가 방법 인 Interlocked.Increment 를 사용 합 니 다.함 수 는 int Increment(ref int num)로 정의 합 니 다.이 대상 은 원자 적 인 변 수 를 제공 하여 자체 증가 작업 을 하고 목표 수 치 를 전달 하 며 되 돌아 오 거나 ref num 은 모두 증가 한 결과 입 니 다.이전의 기초 위 에서 우 리 는 약간의 코드 를 추가 했다.

num = 1;
      Console.WriteLine($"num    :" + num.ToString());
      list.AsParallel().ForAll(n =>
      {
        Interlocked.Increment(ref num);
      });
      Console.WriteLine($"     ,  {list.Count}   :" + num.ToString());
      Console.ReadKey();
실행 결 과 를 살 펴 보 겠 습 니 다.

자 물 쇠 를 추가 한 후에 ID 중복 은 해 결 된 셈 입 니 다.사실 너무 일찍 기뻐 하지 마 세 요.정상 적 인 환경 에 ID 가 있 기 때문에 우 리 는 이런 ID 로 대상 을 구축 할 수 있 습 니 다.그래서 코드 를 썼 습 니 다.집합 으로 이런 ID 를 추 가 했 습 니 다.더욱 진실 한 모 의 생산 환경 을 위해 저 는 forAll 에 순환 코드 를 다음 과 같이 추 가 했 습 니 다.

num = 1;
      Random random = new Random();
      var total = 0;
      var m = new ConcurrentBag<int>();
      list.AsParallel().ForAll(n =>
      {
        var c = random.Next(1, 50);
        Interlocked.Add(ref total, c);
        for (int i = 0; i < c; i++)
        {
          Interlocked.Increment(ref num);
          m.Add(num);
        }
      });
      Console.WriteLine($"     ,  +    {list.Count}   :" + num.ToString());
      Console.WriteLine($"    :{total + 1}");
      var l = m.GroupBy(n => n).Where(o => o.Count() > 1);
      Console.WriteLine($"          ConcurrentBag  num,     :{l.Count()} ");
      Console.ReadKey();

위의 코드 에서 저 는 스 레 드 안전 집합 ConcurrentBag를 사 용 했 습 니 다.그의 네 임 스페이스 는 using System.collections.Concurrent 입 니 다.스 레 드 안전 집합 을 사 용 했 지만 동시 다발 앞 에 서 는 안전 하지 않 습 니 다.여기 서 는 사실 답답 합 니 다.자 물 쇠 를 추가 하고 안전 집합 내부 에 도 자 물 쇠 를 사 용 했 을 것 입 니 다.그러나 중복 되 었 습 니 다.다 중 스 레 드 가 실 행 될 때 문맥 대상 이 있 습 니 다.즉,여러 스 레 드 가 동시에 작업 을 수행 할 때 변 수 를 공 유 했 습 니 다.그들 이 처음에 들 어 온 대상 수 치 는 같 아야 합 니 다.변수 가 증가 할 때 자 물 쇠 를 추 가 했 기 때문에 ID 는 중복 되 지 않 습 니 다.문 제 는 add 방법 에 있 을 것 이 라 고 추측 합 니 다.즉,num 값 이 증가 한 후에 전달 되 지 못 하고 add 방법 을 실 행 했 기 때문에 중복 변 수 를 추가 한 것 입 니 다.그래서 저 는 코드 를 다시 써 서 ID 자체 증가 와 집합 추 가 를 모두 자물쇠 안에 넣 었 습 니 다.

num = 1;
      total = 0;
      using (var q = new BlockingCollection<int>())
      {
        list.AsParallel().ForAll(n =>
        {
          var c = random.Next(1, 50);
          Interlocked.Add(ref total, c);
          for (int i = 0; i < c; i++)
          {
            
            // Task.Delay(100);
            q.Add(Interlocked.Increment(ref num));
            
            //  
            //lock (objLock)
            //{
            //  num++;
            //  q.Add(num);
            //}
          }

        });
        q.CompleteAdding();
        Console.WriteLine($"num    :{total},      :{num}");
        var x = q.GroupBy(n => n).Where(o => o.Count() > 1);
        Console.WriteLine($"        BlockingCollection+Interlocked  num,     :{x.Count()} ");
        Console.ReadKey();
      }
여기 서 저 는 다른 스 레 드 의 안전 한 집합 Blocking Collection 을 테스트 했 습 니 다.이 집합 에 대한 사용 은 MSDN 문 서 를 직접 찾 으 십시오.위의 관건 적 인 코드 는 안전 집합 의 반환 값 을 직접 추가 하면 집합 이 중복 되 지 않도록 보장 할 수 있 습 니 다.그러나 사실은 아래 의 lock 은 정식 환경 에 더욱 적 용 됩 니 다.왜냐하면 저희 가 추가 한 것 은 대상 이 기본 적 인 유형 수치 가 아니 기 때 문 입 니 다.실행 결 과 는 다음 과 같 습 니 다.

이로써 우리 의 문 제 는 해결 되 었 고 계산 시간 이 원래 의 9 분 여 에서 110 초 정도 로 떨 어 졌 다.이 를 통 해 알 수 있 듯 이 Parallel 의 처 리 는 매우 기 똥 차 고 유일 하 게 부족 한 것 은 CPU 를 차지 하 며 계산 을 실행 한 후에 CPU 는 88%에 달 했다.계산 결과 첨부:

전후 대비 최적화

요약:
C\#안전 집합 이 병발 하 는 상황 에서 반드시 안전 한 것 이 아니 라 실제 응용 장면 과 검증 결 과 를 결합 시 켜 야 한다.Parallel.Foreach 는 순환 수량 이 상당 한 상황 에서 사용 할 수 있 습 니 다.공유 변수 가 있 으 면 자물쇠 와 함께 동기 화 처리 해 야 합 니 다.이 방법 을 신중하게 사용 해 야 합 니 다.만약 에 방법 내부 에 데이터 베 이 스 를 조작 하 는 기억 이 있 으 면 사무 처 리 를 늘 려 야 합 니 다.그렇지 않 으 면 하하 합 니 다.
자,이상 이 이 글 의 모든 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가 치 를 가지 기 를 바 랍 니 다.여러분 의 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기