อะไรคือฟังก์ชั่น reentrant


199

ส่วนใหญ่ ของ ครั้งความหมายของ reentrance ที่ยกมาจากวิกิพีเดีย :

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

  1. ต้องไม่เก็บข้อมูลที่ไม่คงที่ (หรือทั่วโลก)
  2. ต้องไม่ส่งคืนที่อยู่เป็นข้อมูลคงที่ (หรือทั่วโลก) ที่ไม่ใช่ค่าคงที่
  3. จะต้องทำงานเฉพาะกับข้อมูลที่ผู้โทรแจ้งมาเท่านั้น
  4. ต้องไม่พึ่งพาการล็อกกับรีซอร์สเดี่ยว
  5. ต้องไม่แก้ไขโค้ดของตัวเอง (ยกเว้นว่าจะดำเนินการในพื้นที่จัดเก็บเธรดเฉพาะของตนเอง)
  6. ต้องไม่เรียกโปรแกรมคอมพิวเตอร์หรือรูทีนที่ไม่ใช่ reentrant

เป็นวิธีการที่ปลอดภัยที่กำหนดไว้?

หากโปรแกรมสามารถดำเนินการได้อย่างปลอดภัยพร้อมกันก็หมายความว่ามันเป็น reentrant?

อะไรคือเธรดทั่วไประหว่างจุดทั้งหกที่กล่าวถึงว่าฉันควรจำไว้ในขณะที่ตรวจสอบรหัสของฉันสำหรับความสามารถ reentrant

นอกจากนี้

  1. ฟังก์ชั่นการเรียกซ้ำทั้งหมดเป็น reentrant หรือไม่
  2. ฟังก์ชั่น thread-safe ทั้งหมดถูก reentrant หรือไม่
  3. ฟังก์ชันการเรียกซ้ำทั้งหมดและแบบปลอดภัยต่อการส่งข้อความ reentrant ทั้งหมดหรือไม่

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


6
จริงๆแล้วฉันไม่เห็นด้วยกับ # 2 ในรายการแรก คุณสามารถส่งคืนที่อยู่ไปยังสิ่งที่คุณต้องการจากฟังก์ชั่นผู้เข้าร่วมอีกครั้งข้อ จำกัด คือสิ่งที่คุณทำกับที่อยู่นั้นในรหัสโทร

2
@ Neil แต่ในฐานะนักเขียนของฟังก์ชั่น reentrant ไม่สามารถควบคุมสิ่งที่ผู้โทรแน่นอนพวกเขาจะต้องไม่ส่งคืนที่อยู่ไปยังข้อมูลที่ไม่คงที่ (หรือทั่วโลก) คงที่เพื่อให้เป็น reentrant อย่างแท้จริง?
Robben_Ford_Fan_boy

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

"ปลอดภัยสำหรับเธรด" นั้นไม่มีความหมายนอกจากว่าคุณจะระบุว่าเธรดกำลังทำอะไรและเอฟเฟกต์ที่คาดหวังของการทำนั้นเป็นอย่างไร แต่บางทีนั่นอาจเป็นคำถามแยกต่างหาก

ฉันหมายถึงอย่างปลอดภัยพฤติกรรมมีการกำหนดไว้อย่างดีและกำหนดขึ้นโดยไม่คำนึงถึงการกำหนดเวลา
AturSams

คำตอบ:


192

1. กำหนดไว้อย่างปลอดภัยอย่างไร

ความหมาย ในกรณีนี้นี่ไม่ใช่คำที่กำหนดยาก มันหมายถึง "คุณสามารถทำได้โดยไม่มีความเสี่ยง"

2. หากโปรแกรมสามารถดำเนินการได้อย่างปลอดภัยพร้อมกันหมายความว่าเป็น reentrant เสมอหรือไม่

เลขที่

ตัวอย่างเช่นลองมีฟังก์ชั่น C ++ ที่รับทั้งการล็อกและการเรียกกลับเป็นพารามิเตอร์:

#include <mutex>

typedef void (*callback)();
std::mutex m;

void foo(callback f)
{
    m.lock();
    // use the resource protected by the mutex

    if (f) {
        f();
    }

    // use the resource protected by the mutex
    m.unlock();
}

ฟังก์ชั่นอื่นอาจจำเป็นต้องล็อค mutex เดียวกัน:

void bar()
{
    foo(nullptr);
}

ตั้งแต่แรกเห็นทุกอย่างดูเหมือนจะโอเค… แต่เดี๋ยวก่อน:

int main()
{
    foo(bar);
    return 0;
}

หากการล็อก mutex ไม่เกิดขึ้นซ้ำนี่คือสิ่งที่จะเกิดขึ้นในเธรดหลัก:

  1. mainfooจะเรียก
  2. foo จะได้รับการล็อค
  3. fooจะเรียกซึ่งจะเรียกbarfoo
  4. ที่ 2 fooจะพยายามที่จะได้รับล็อคล้มเหลวและรอให้มันถูกปล่อยออกมา
  5. การหยุดชะงัก
  6. โอ๊ะโอ ...

ตกลงฉันโกงโดยใช้สิ่งที่โทรกลับ แต่มันง่ายที่จะจินตนาการว่าโค้ดที่ซับซ้อนกว่านี้มีเอฟเฟกต์ที่คล้ายกัน

3. อะไรคือประเด็นร่วมกันระหว่างจุดทั้งหกที่กล่าวถึงว่าฉันควรจำไว้ในขณะที่ตรวจสอบรหัสของฉันสำหรับความสามารถ reentrant?

คุณสามารถดมกลิ่นปัญหาหากฟังก์ชั่นของคุณมี / ให้การเข้าถึงทรัพยากรถาวรที่แก้ไขได้หรือมี / ให้การเข้าถึงฟังก์ชั่นที่มีกลิ่นมีกลิ่น

( ตกลง 99% ของรหัสเราควรได้กลิ่นแล้ว ... ดูส่วนสุดท้ายเพื่อจัดการกับ ... )

ดังนั้นการศึกษารหัสของคุณหนึ่งในจุดเหล่านั้นควรเตือนคุณ:

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

โปรดทราบว่า non-reentrancy เป็นไวรัส: ฟังก์ชันที่สามารถเรียกใช้ฟังก์ชันที่ไม่ใช่ reentrant ที่เป็นไปได้ไม่สามารถพิจารณา reentrant

โปรดทราบว่าวิธีการ C ++ นั้นมีกลิ่นเพราะสามารถเข้าถึงthisดังนั้นคุณควรศึกษารหัสเพื่อให้แน่ใจว่าพวกเขาไม่มีปฏิสัมพันธ์ที่ตลก

4.1 ฟังก์ชั่นการเรียกซ้ำทั้งหมดเป็น reentrant หรือไม่

เลขที่

ในกรณีแบบมัลติเธรดฟังก์ชันเรียกซ้ำที่เข้าถึงทรัพยากรที่ใช้ร่วมกันสามารถเรียกใช้โดยหลายเธรดในเวลาเดียวกันส่งผลให้ข้อมูลไม่ถูกต้อง / เสียหาย

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

4.2 ฟังก์ชั่น thread-safe ทั้งหมดถูก reentrant หรือไม่

ในตัวอย่างข้างต้นฉันแสดงให้เห็นว่าฟังก์ชั่น threadsafe ไม่ได้เป็น reentrant ตกลงฉันโกงเพราะพารามิเตอร์โทรกลับ แต่จากนั้นมีหลายวิธีในการปิดกั้นเธรดโดยให้มันล็อคสองครั้งแบบไม่เรียกซ้ำ

4.3 ฟังก์ชันการเรียกซ้ำทั้งหมดและแบบปลอดภัยต่อการส่งข้อความซ้ำทั้งหมด

ฉันจะพูดว่า "ใช่" ถ้า "เรียกซ้ำ" คุณหมายถึง "ปลอดภัยแบบเรียกซ้ำ"

หากคุณสามารถรับประกันได้ว่าฟังก์ชั่นสามารถเรียกใช้พร้อมกันโดยหลายกระทู้และสามารถเรียกตัวเองไม่ว่าโดยตรงหรือโดยอ้อมโดยไม่มีปัญหาแล้วมันจะ reentrant

ปัญหากำลังประเมินการรับประกันนี้… ^ _ ^

5. เงื่อนไขเช่น reentrance และความปลอดภัยของด้ายนั้นแน่นอนเช่นพวกเขามีคำจำกัดความที่แน่นอนคอนกรีตหรือไม่

ฉันเชื่อว่าพวกเขาทำ แต่จากนั้นการประเมินฟังก์ชั่นเป็น thread-safe หรือ reentrant อาจเป็นเรื่องยาก นี่คือเหตุผลที่ฉันใช้คำว่ากลิ่นด้านบน: คุณสามารถค้นหาฟังก์ชั่นไม่ได้เป็น reentrant แต่มันอาจเป็นเรื่องยากที่จะตรวจสอบให้แน่ใจว่าโค้ดที่ซับซ้อนนั้นเป็น reentrant

6. ตัวอย่าง

สมมติว่าคุณมีวัตถุด้วยวิธีหนึ่งที่ต้องใช้ทรัพยากร:

struct MyStruct
{
    P * p;

    void foo()
    {
        if (this->p == nullptr)
        {
            this->p = new P();
        }

        // lots of code, some using this->p

        if (this->p != nullptr)
        {
            delete this->p;
            this->p = nullptr;
        }
    }
};

ปัญหาแรกคือถ้าฟังก์ชั่นนี้ถูกเรียกซ้ำ (เช่นฟังก์ชั่นนี้เรียกตัวเองไม่ว่าโดยตรงหรือโดยอ้อม) รหัสอาจจะผิดพลาดเพราะ this->pจะถูกลบในตอนท้ายของการโทรครั้งสุดท้ายและยังคงใช้ก่อนสิ้น ของสายแรก

ดังนั้นรหัสนี้ไม่ปลอดภัยซ้ำ

เราสามารถใช้ตัวนับอ้างอิงเพื่อแก้ไขสิ่งนี้:

struct MyStruct
{
    size_t c;
    P * p;

    void foo()
    {
        if (c == 0)
        {
            this->p = new P();
        }

        ++c;
        // lots of code, some using this->p
        --c;

        if (c == 0)
        {
            delete this->p;
            this->p = nullptr;
        }
    }
};

วิธีนี้รหัสจะกลายเป็นแบบเรียกซ้ำปลอดภัย ... แต่ก็ยังไม่ได้ reentrant เนื่องจากปัญหาแบบมัลติเธรด: เราต้องแน่ใจว่าการแก้ไขcและpจะทำแบบอะตอมโดยใช้mutex แบบเรียกซ้ำ (ไม่ใช่ mutex ทั้งหมดที่เรียกซ้ำ):

#include <mutex>

struct MyStruct
{
    std::recursive_mutex m;
    size_t c;
    P * p;

    void foo()
    {
        m.lock();

        if (c == 0)
        {
            this->p = new P();
        }

        ++c;
        m.unlock();
        // lots of code, some using this->p
        m.lock();
        --c;

        if (c == 0)
        {
            delete this->p;
            this->p = nullptr;
        }

        m.unlock();
    }
};

และแน่นอนทั้งหมดนี้ถือว่าlots of codeเป็นตัวเอง reentrant pรวมถึงการใช้งานของ

และรหัสข้างต้นไม่ได้ยกเว้นความปลอดภัยจากระยะไกลแต่นี่เป็นอีกเรื่องหนึ่ง… ^ _ ^

7. เฮ้ 99% ของรหัสของเราไม่ใช่ reentrant!

มันค่อนข้างจริงสำหรับรหัสสปาเก็ตตี้ แต่ถ้าคุณแบ่งพาร์ติชันรหัสของคุณอย่างถูกต้องคุณจะหลีกเลี่ยงปัญหา reentrancy

7.1 ตรวจสอบให้แน่ใจว่าฟังก์ชั่นทั้งหมดไม่มีสถานะ

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

7.2 ตรวจสอบให้แน่ใจว่าวัตถุของคุณ "ปลอดภัยแบบเรียกซ้ำ"

วิธีวัตถุมีการเข้าถึงthisดังนั้นจึงใช้สถานะร่วมกับวิธีการทั้งหมดของอินสแตนซ์เดียวกันของวัตถุ

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

7.3 ตรวจสอบให้แน่ใจว่าวัตถุทั้งหมดของคุณถูกห่อหุ้มอย่างถูกต้อง

ไม่มีใครควรเข้าถึงข้อมูลภายในได้:

    // bad
    int & MyObject::getCounter()
    {
        return this->counter;
    }

    // good
    int MyObject::getCounter()
    {
        return this->counter;
    }

    // good, too
    void MyObject::getCounter(int & p_counter)
    {
        p_counter = this->counter;
    }

แม้การส่งคืนการอ้างอิง const อาจเป็นอันตรายหากผู้ใช้ดึงข้อมูลที่อยู่ของข้อมูลเนื่องจากบางส่วนของรหัสสามารถแก้ไขได้โดยไม่ต้องมีรหัสที่อ้างอิงการอ้างอิง const

7.4 ตรวจสอบให้แน่ใจว่าผู้ใช้รู้ว่าวัตถุของคุณไม่ปลอดภัยสำหรับเธรด

ดังนั้นผู้ใช้มีหน้าที่รับผิดชอบในการใช้ mutexes เพื่อใช้วัตถุที่ใช้ร่วมกันระหว่างกระทู้

วัตถุจาก STL ได้รับการออกแบบให้ไม่ปลอดภัยต่อเธรด (เนื่องจากปัญหาด้านประสิทธิภาพ) และดังนั้นหากผู้ใช้ต้องการแชร์ std::stringระหว่างสองเธรดผู้ใช้จะต้องปกป้องการเข้าถึงด้วยการทำงานพร้อมกัน

7.5 ตรวจสอบให้แน่ใจว่ารหัส thread-safe ของคุณปลอดภัยซ้ำ

ซึ่งหมายความว่าการใช้ mutexes แบบเรียกซ้ำหากคุณเชื่อว่าทรัพยากรเดียวกันสามารถใช้สองครั้งโดยเธรดเดียวกัน


1
ในการพูดคลุมเครือเล็กน้อยฉันจริง ๆ แล้วคิดว่าในกรณีนี้มีการกำหนด "ความปลอดภัย" - หมายความว่าฟังก์ชั่นจะทำหน้าที่เฉพาะกับตัวแปรที่มีให้ - กล่าวคือมันเป็นการย่อสำหรับคำจำกัดความด้านล่าง และประเด็นก็คือนี่อาจไม่ได้หมายความถึงความคิดเรื่องความปลอดภัยอื่น ๆ
Joe Soul-bringer

คุณพลาดการส่งผ่าน mutex ในตัวอย่างแรกหรือไม่?
detly

@ paercebal: ตัวอย่างของคุณผิด คุณไม่จำเป็นต้องกังวลกับการติดต่อกลับการเรียกซ้ำง่าย ๆ จะมีปัญหาเดียวกันหากมีหนึ่งอย่างไรก็ตามปัญหาเดียวคือคุณลืมที่จะบอกว่าที่จัดสรรล็อค
Yttrill

3
@Yttrill: ฉันถือว่าคุณกำลังพูดถึงตัวอย่างแรก ฉันใช้ "การโทรกลับ" เพราะโดยพื้นฐานแล้วการโทรกลับมีกลิ่น แน่นอนว่าฟังก์ชั่นวนซ้ำจะมีปัญหาเดียวกัน แต่โดยทั่วไปแล้วสามารถวิเคราะห์ฟังก์ชั่นได้อย่างง่ายดายและธรรมชาติที่เรียกซ้ำได้ของมันจึงตรวจสอบว่ามันเป็น reentrant หรือไม่ก็สามารถเรียกซ้ำได้ ในทางกลับกันหมายความว่าผู้เขียนฟังก์ชั่นการโทรกลับไม่มีข้อมูลใด ๆ เกี่ยวกับสิ่งที่โทรกลับทำดังนั้นผู้เขียนคนนี้อาจพบว่ามันยากที่จะตรวจสอบว่าการทำงานของเขา / เธอเป็น reentrant นี่เป็นปัญหาที่ฉันต้องการแสดง
paercebal

1
@Gab 是好人: ฉันแก้ไขตัวอย่างแรกแล้ว ขอบคุณ! ตัวจัดการสัญญาณจะมาพร้อมกับปัญหาของตัวเองแตกต่างจากการแสดงซ้ำตามปกติเมื่อสัญญาณเพิ่มขึ้นคุณจะไม่สามารถทำอะไรได้เลยนอกจากการเปลี่ยนตัวแปรโกลบอลที่ประกาศโดยเฉพาะ
paercebal

21

"Safely" ถูกกำหนดอย่างถูกต้องตามสามัญสำนึกที่กำหนด - มันหมายถึง "การทำสิ่งนั้นอย่างถูกต้องโดยไม่รบกวนสิ่งอื่น" หกจุดที่คุณอ้างถึงค่อนข้างชัดเจนแสดงความต้องการเพื่อให้บรรลุ

คำตอบสำหรับคำถาม 3 ข้อของคุณคือ 3 × "ไม่"


ฟังก์ชั่นการเรียกซ้ำทั้งหมดเป็น reentrant หรือไม่

NO!

การเรียกใช้ซ้ำของฟังก์ชัน recursive พร้อมกันสองรายการสามารถทำให้เสียซึ่งกันและกันได้อย่างง่ายดายหากพวกเขาเข้าถึงข้อมูลโกลบอล / สถิตเดียวกัน


ฟังก์ชั่น thread-safe ทั้งหมดถูก reentrant หรือไม่

NO!

ฟังก์ชั่นเป็น thread-safe ถ้ามันไม่ทำงานผิดปกติถ้าเรียกพร้อมกัน แต่สามารถทำได้เช่นโดยใช้ mutex เพื่อบล็อกการดำเนินการของการภาวนาที่สองจนกว่าจะเสร็จสิ้นครั้งแรกดังนั้นการร้องขอเพียงครั้งเดียวทำงานได้ในแต่ละครั้ง Reentrancy วิธีการดำเนินการควบคู่กันไปโดยไม่รบกวนการสวดอื่น


ฟังก์ชันการเรียกซ้ำทั้งหมดและแบบปลอดภัยต่อการส่งข้อความ reentrant ทั้งหมดหรือไม่

NO!

ดูด้านบน.


10

กระทู้ทั่วไป:

มีการกำหนดพฤติกรรมที่ดีหรือไม่หากรูทีนถูกเรียกขณะที่ถูกขัดจังหวะ?

หากคุณมีฟังก์ชั่นเช่นนี้:

int add( int a , int b ) {
  return a + b;
}

จากนั้นจะไม่ขึ้นอยู่กับสถานะภายนอกใด ๆ พฤติกรรมที่กำหนดไว้อย่างดี

หากคุณมีฟังก์ชั่นเช่นนี้:

int add_to_global( int a ) {
  return gValue += a;
}

ผลลัพธ์ไม่ได้ถูกกำหนดไว้อย่างดีในหลายเธรด ข้อมูลอาจสูญหายได้หากกำหนดเวลาผิด

รูปแบบที่ง่ายที่สุดของฟังก์ชั่น reentrant เป็นสิ่งที่ดำเนินการเฉพาะในการโต้แย้งผ่านและค่าคงที่ สิ่งอื่นใดที่ต้องจัดการเป็นพิเศษหรือบ่อยครั้งที่ไม่ได้เป็นผู้ให้คำสั่งซื้อซ้ำ และแน่นอนว่าข้อโต้แย้งนั้นจะต้องไม่อ้างอิงถึงความกลมกลืนของข้อมูล


7

ตอนนี้ฉันต้องอธิบายความคิดเห็นก่อนหน้าของฉัน @ paercebal คำตอบไม่ถูกต้อง ในรหัสตัวอย่างไม่มีใครสังเกตเห็นว่า mutex ซึ่งควรจะเป็นพารามิเตอร์ไม่ได้ผ่านจริงหรือ

ฉันโต้เถียงข้อสรุปฉันยืนยัน: เพื่อให้ฟังก์ชั่นมีความปลอดภัยในการปรากฏตัวพร้อมกันนั้นจะต้องเป็นผู้เข้าร่วมอีกครั้ง ดังนั้นจึงปลอดภัยพร้อมกัน (โดยปกติแล้วจะเขียนความปลอดภัยของเธรด) แสดงถึงผู้เข้าร่วมใหม่

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

ตัวอย่างเช่น memcpy () เป็น thread-safe และ re-entrant (ปกติ) เห็นได้ชัดว่ามันจะไม่ทำงานอย่างที่คาดไว้ถ้าเรียกด้วยตัวชี้ไปยังเป้าหมายเดียวกันจากสองเธรดที่แตกต่างกัน นั่นคือจุดประสงค์ของคำจำกัดความของ SGI การวางความรับผิดชอบบนไคลเอนต์เพื่อให้แน่ใจว่าการเข้าถึงโครงสร้างข้อมูลเดียวกันจะถูกซิงโครไนซ์โดยไคลเอนต์

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

ประเด็นก็คือ "ความเสียหาย" ไม่จำเป็นที่จะต้องทำให้หน่วยความจำในคอมพิวเตอร์ของคุณมีการเขียนที่ไม่ถูกบันทึก: ความเสียหายยังสามารถเกิดขึ้นได้แม้ว่าการดำเนินการทั้งหมดจะถูกทำให้เป็นอนุกรม มันจะตามมาเมื่อคุณถามว่าฟังก์ชั่นเป็น thread-safe หรือ re-entrant คำถามนั้นหมายถึงอาร์กิวเมนต์ที่แยกออกจากกันอย่างเหมาะสมทั้งหมด: การใช้อากิวเมนต์แบบคู่ไม่ได้เป็นตัวอย่างที่ตรงกันข้าม

มีระบบการเขียนโปรแกรมจำนวนมากออกมี: Ocaml เป็นหนึ่งและฉันคิดว่า Python เช่นกันซึ่งมีรหัสที่ไม่ใช่ reentrant จำนวนมากในพวกเขา แต่ที่ใช้ล็อคระดับโลกเพื่อ interleave เธรด acesss ระบบเหล่านี้ไม่ได้เข้าร่วมอีกครั้งและพวกเขาไม่ได้เป็นเธรดที่ปลอดภัยหรือพร้อมกันที่ปลอดภัยพวกเขาทำงานอย่างปลอดภัยเพียงเพราะพวกเขาป้องกันไม่ให้เกิดขึ้นพร้อมกันทั่วโลก

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

malloc(heap*, size_t);

ตอนนี้มันสามารถปลอดภัยได้แล้วเพราะมันจะถ่ายโอนความรับผิดชอบในการเข้าถึงการแชร์แบบอนุกรมให้กับฮีปเดียวไปยังไคลเอนต์ โดยเฉพาะอย่างยิ่งไม่จำเป็นต้องทำงานหากมีวัตถุกองแยกต่างหาก หากใช้ฮีปทั่วไปลูกค้าจะต้องเข้าใช้งานซีเรียลไลซ์เซชั่น การใช้ตัวล็อคภายในฟังก์ชั่นนั้นไม่เพียงพอ: เพียงแค่พิจารณาการล็อค malloc ของ heap * จากนั้นสัญญาณก็มาพร้อมกับการเรียก malloc บนตัวชี้เดียวกัน: การหยุดชะงัก: สัญญาณไม่สามารถดำเนินการได้และไคลเอนต์ไม่สามารถทำได้ ถูกขัดจังหวะ

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


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

3

"เธรดทั่วไป" (pun ตั้งใจ!?) ระหว่างจุดที่ระบุไว้คือฟังก์ชั่นจะต้องไม่ทำอะไรที่จะส่งผลกระทบต่อพฤติกรรมของการโทรซ้ำหรือการโทรซ้ำพร้อมกันในฟังก์ชั่นเดียวกัน

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

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

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

ไม่จำเป็นต้องมีฟังก์ชั่นเกลียวที่ปลอดภัย มันอาจบรรลุความปลอดภัยด้ายโดยเฉพาะป้องกัน reentrancy ด้วยการล็อคและจุด (6) กล่าวว่าฟังก์ชั่นดังกล่าวไม่ได้ reentrant เกี่ยวกับจุด (6) ฟังก์ชั่นที่เรียกใช้ฟังก์ชั่น thread-safe ที่ล็อคไม่ปลอดภัยสำหรับใช้ในการเรียกซ้ำ (มันจะล็อคตาย) และดังนั้นจึงไม่ได้กล่าวว่าเป็นการ reentrant แม้ว่ามันอาจจะปลอดภัยสำหรับการเกิดขึ้นพร้อมกัน จะยังคงเป็นผู้เข้าร่วมใหม่ในแง่ที่ว่าหลายกระทู้สามารถมีตัวนับโปรแกรมของพวกเขาในฟังก์ชั่นดังกล่าวพร้อมกัน (ไม่ได้อยู่ในภูมิภาคล็อค) อาจเป็นสิ่งนี้ช่วยแยกแยะความปลอดภัยของเธรดจาก reentarncy (หรืออาจเพิ่มความสับสนของคุณ!)


1

คำตอบของคำถาม "ยัง" คือ "ไม่", "ไม่" และ "ไม่" เพียงเพราะฟังก์ชั่นซ้ำและ / หรือเธรดที่ปลอดภัยก็ไม่ได้ทำให้มันเข้าใหม่

ฟังก์ชันประเภทนี้แต่ละประเภทสามารถล้มเหลวได้ในทุกจุดที่คุณเสนอราคา (แม้ว่าฉันจะไม่แน่ใจ 100% ของจุดที่ 5)


1

คำว่า "Thread-safe" และ "re-entrant" หมายถึงเฉพาะและสิ่งที่คำจำกัดความของพวกเขาพูด "ปลอดภัย" ในบริบทนี้หมายถึงเพียงสิ่งที่ความหมายที่คุณพูดด้านล่างก็กล่าวว่า

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

ฟังก์ชั่นเรียกซ้ำสามารถเป็นอะไรก็ได้และผู้เข้าร่วมใหม่มีความหมายที่แข็งแกร่งกว่าเธรดที่ปลอดภัยดังนั้นคำตอบสำหรับคำถามที่มีหมายเลขของคุณนั้นไม่ใช่ทั้งหมด

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

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

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