รายการเพิ่ม () ความปลอดภัยของเธรด


91

ฉันเข้าใจว่าโดยทั่วไปรายการไม่ปลอดภัยต่อเธรดอย่างไรก็ตามมีอะไรผิดปกติกับการเพิ่มรายการลงในรายการหากเธรดไม่เคยดำเนินการอื่นใดในรายการ (เช่นการข้ามผ่าน)

ตัวอย่าง:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});

3
รายการที่ซ้ำกันอย่างแน่นอนของความปลอดภัยของเธรด <T>
JK

ครั้งหนึ่งฉันเคยใช้รายการ <T> เพื่อเพิ่มวัตถุใหม่จากหลาย ๆ งานที่ทำงานแบบขนาน บางครั้งหายากมากเมื่อทำซ้ำในรายการหลังจากที่งานทั้งหมดเสร็จสิ้นลงด้วยเร็กคอร์ดที่เป็นโมฆะซึ่งหากไม่มีเธรดเพิ่มเติมเข้ามาเกี่ยวข้องก็จะเป็นไปไม่ได้ในทางปฏิบัติที่สิ่งนี้จะเกิดขึ้น ฉันเดาว่าเมื่อรายการได้รับการจัดสรรองค์ประกอบภายในใหม่เพื่อขยายเธรดอื่นทำให้มันยุ่งเหยิงโดยพยายามเพิ่มวัตถุอื่น ดังนั้นจึงไม่ใช่ความคิดที่ดีที่จะทำเช่นนั้น!
osmiumbin

สิ่งที่ฉันเห็นในขณะนี้ @osmiumbin เกี่ยวกับวัตถุนั้นเป็นโมฆะอย่างอธิบายไม่ได้เมื่อเพิ่มจากหลายเธรด ขอบคุณสำหรับการยืนยัน
Blackey

คำตอบ:


75

เบื้องหลังมีสิ่งต่างๆเกิดขึ้นมากมายรวมถึงการจัดสรรบัฟเฟอร์ใหม่และการคัดลอกองค์ประกอบ รหัสนั้นจะก่อให้เกิดอันตราย ง่ายมากไม่มีการดำเนินการเกี่ยวกับอะตอมเมื่อเพิ่มลงในรายการอย่างน้อยที่สุดคุณสมบัติ "ความยาว" จะต้องมีการอัปเดตและรายการจะต้องวางในตำแหน่งที่ถูกต้องและ (หากมีตัวแปรแยกต่างหาก) ดัชนีต้องการ ที่จะปรับปรุง เธรดหลายเธรดสามารถเหยียบย่ำกันและกันได้ และหากต้องการการเติบโตก็จะมีอีกมากมายเกิดขึ้น หากมีบางสิ่งที่เขียนลงในรายการไม่มีสิ่งอื่นใดที่ควรอ่านหรือเขียนลงไป

ใน. NET 4.0 เรามีคอลเล็กชันพร้อมกันซึ่งเป็นเธรดที่สะดวกและไม่ต้องใช้การล็อก


นั่นเป็นเหตุผลที่ดีอย่างแน่นอนฉันจะดูคอลเลกชันพร้อมกันใหม่สำหรับสิ่งนี้ ขอขอบคุณ.
e36M3

11
โปรดทราบว่าไม่มีConcurrentListประเภทในตัว มีกระเป๋าพร้อมกันพจนานุกรมกองคิว ฯลฯ แต่ไม่มีรายการ
LukeH

11

แนวทางปัจจุบันของคุณไม่ปลอดภัยต่อเธรด - ฉันขอแนะนำให้หลีกเลี่ยงสิ่งนี้โดยสิ้นเชิง - เนื่องจากโดยพื้นฐานแล้วคุณทำการแปลงข้อมูล PLINQ อาจเป็นแนวทางที่ดีกว่า (ฉันรู้ว่านี่เป็นตัวอย่างที่เรียบง่าย แต่ในท้ายที่สุดคุณกำลังคาดการณ์แต่ละธุรกรรมเป็นสถานะอื่น "วัตถุ).

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();

ฉันนำเสนอตัวอย่างที่เรียบง่ายมากเกินไปเพื่อเน้นย้ำในแง่มุมของ List เพิ่มสิ่งที่ฉันสนใจ My Parallel ในความเป็นจริงแล้วต่างประเทศจะทำงานได้ดีและจะไม่ใช่การแปลงข้อมูลง่ายๆ ขอบคุณ.
e36M3

4
คอลเลกชันที่ทำงานพร้อมกันสามารถทำให้ประสิทธิภาพการทำงานแบบขนานของคุณพิการได้หากใช้โดยไม่จำเป็นสิ่งอื่นที่คุณสามารถทำได้คือใช้อาร์เรย์ขนาดคงที่และใช้การParallel.Foreachโอเวอร์โหลดที่ใช้ในดัชนี - ในกรณีนี้แต่ละเธรดกำลังจัดการกับรายการอาร์เรย์ที่แตกต่างกันและคุณควรปลอดภัย
BrokenGlass

6

ไม่ใช่เรื่องที่ไม่สมควรถาม มีหลายกรณีที่วิธีการที่อาจทำให้เกิดปัญหาด้านความปลอดภัยของเธรดร่วมกับวิธีอื่น ๆ นั้นปลอดภัยหากเป็นวิธีเดียวที่เรียกว่า

อย่างไรก็ตามนี่ไม่ใช่กรณีอย่างชัดเจนเมื่อคุณพิจารณารหัสที่แสดงในตัวสะท้อน:

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
        this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

แม้ว่าจะEnsureCapacityอยู่ในเธรดที่ปลอดภัย (และแน่นอนที่สุดก็คือไม่) แต่โค้ดด้านบนนั้นจะไม่ปลอดภัยต่อเธรดอย่างชัดเจนเมื่อพิจารณาถึงความเป็นไปได้ของการโทรพร้อมกันไปยังตัวดำเนินการที่เพิ่มขึ้นทำให้เกิดการเขียนผิดพลาด

ไม่ว่าจะเป็นการล็อกใช้ ConcurrentList หรืออาจใช้คิวที่ไม่มีการล็อกเป็นสถานที่ที่หลายเธรดเขียนถึงและอ่านจากมันไม่ว่าจะโดยตรงหรือโดยการกรอกรายการด้วย - หลังจากที่พวกเขาทำงานเสร็จแล้ว (ฉันสมมติว่า การเขียนพร้อมกันหลายรายการตามด้วยการอ่านแบบเธรดเดียวเป็นรูปแบบของคุณที่นี่โดยตัดสินจากคำถามของคุณเนื่องจากเป็นอย่างอื่นฉันไม่สามารถดูได้ว่าเงื่อนไขที่Addวิธีการเดียวที่เรียกว่าสามารถใช้ประโยชน์ได้อย่างไร)


6

หากคุณต้องการใช้List.addจากหลายเธรดและไม่สนใจเกี่ยวกับการสั่งซื้อคุณอาจไม่จำเป็นต้องมีความสามารถในการจัดทำดัชนีอีกListต่อไปและควรใช้คอลเลกชันที่มีอยู่พร้อมกันบางส่วนแทน

หากคุณเพิกเฉยต่อคำแนะนำนี้และทำเพียงอย่างเดียวaddคุณสามารถทำให้addเธรดปลอดภัย แต่ในลำดับที่คาดเดาไม่ได้เช่นนี้:

private Object someListLock = new Object(); // only once

...

lock (someListLock)
{
    someList.Add(item);
}

someList[i]ถ้าคุณยอมรับการสั่งซื้อนี้คาดเดาไม่ได้มีโอกาสที่คุณดังกล่าวก่อนหน้าไม่จำเป็นต้องคอลเลกชันที่สามารถทำจัดทำดัชนีในขณะที่


5

สิ่งนี้จะทำให้เกิดปัญหาเนื่องจากรายการถูกสร้างขึ้นบนอาร์เรย์และไม่ปลอดภัยสำหรับเธรดคุณอาจได้รับดัชนีนอกขอบเขตหรือค่าบางค่าที่แทนที่ค่าอื่น ๆ ขึ้นอยู่กับว่าเธรดอยู่ที่ใด โดยพื้นฐานแล้วอย่าทำ

มีปัญหาที่อาจเกิดขึ้นหลายประการ ... อย่าเพิ่งทำ หากคุณต้องการคอลเลกชันที่ปลอดภัยของเธรดให้ใช้การล็อกหรือหนึ่งใน System.Collections.Concurrent



2

มีอะไรผิดปกติในการเพิ่มรายการลงในรายการหากเธรดไม่เคยดำเนินการอื่นใดในรายการ

คำตอบสั้น ๆ : ใช่

คำตอบแบบยาว: เรียกใช้โปรแกรมด้านล่าง

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
    readonly List<int> l = new List<int>();
    const int amount = 1000;
    int toFinish = amount;
    readonly AutoResetEvent are = new AutoResetEvent(false);

    static void Main()
    {
        new Program().Run();
    }

    void Run()
    {
        for (int i = 0; i < amount; i++)
            new Thread(AddTol).Start(i);

        are.WaitOne();

        if (l.Count != amount ||
            l.Distinct().Count() != amount ||
            l.Min() < 0 ||
            l.Max() >= amount)
            throw new Exception("omg corrupted data");

        Console.WriteLine("All good");
        Console.ReadKey();
    }

    void AddTol(object o)
    {
        // uncomment to fix
        // lock (l) 
        l.Add((int)o);

        int i = Interlocked.Decrement(ref toFinish);

        if (i == 0)
            are.Set();
    }
}

@royi คุณใช้งานสิ่งนี้บนเครื่องแกนเดียวหรือไม่?
Bas Smit

สวัสดีฉันคิดว่ามีปัญหากับตัวอย่างนี้เนื่องจากกำลังตั้งค่า AutoResetEvent เมื่อใดก็ตามที่พบหมายเลข 1000 เนื่องจากสามารถประมวลผลเธรดเหล่านี้ได้ทุกเมื่อที่ต้องการสามารถไปถึง 1000 ก่อนที่จะถึง 999 เช่น หากคุณเพิ่ม Console.WriteLine ในวิธีการ AddTol คุณจะเห็นว่าการเรียงลำดับเลขไม่ได้
Dave Walker

@dave เป็นการตั้งค่าเหตุการณ์เมื่อ i == 0
Bas Smit

2

อย่างที่คนอื่นพูดไปแล้วคุณสามารถใช้คอลเลกชันพร้อมกันได้จากSystem.Collections.Concurrentเนมสเปซ หากคุณสามารถใช้หนึ่งในนั้นได้นี่เป็นที่ต้องการ

แต่ถ้าคุณต้องการรายชื่อซึ่งจะตรงเพียงจริงๆคุณสามารถมองไปที่SynchronizedCollection<T>-class System.Collections.Genericใน

โปรดทราบว่าคุณต้องรวมแอสเซมบลี System.ServiceModel ซึ่งเป็นสาเหตุที่ทำให้ฉันไม่ชอบมันมาก แต่บางครั้งฉันก็ใช้มัน


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.