รหัสย่อที่สร้าง Deadlock


11

เขียนรหัสที่สั้นที่สุดเพื่อสร้างการหยุดชะงัก การเรียกใช้โค้ดต้องหยุดชั่วคราวดังนั้นจึงไม่ทำงาน

public class DeadlockFail extends Thread{ //Java code
    public static void main(String[]a){
        Thread t = new DeadlockFail();
        t.start();
        t.join();
    }
    //this part is an infinite loop; continues running the loop. 
    public void run(){while(true){}}
}

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


มันนับว่าเป็นการหยุดชะงักหรือไม่ถ้าฉันพยายามล็อคตัวล็อค (ไม่ใช่ reentrant) เดียวกันสองครั้งจากเธรดเดียวกัน? (ขออภัยฉันไม่ได้ตระหนักถึงคำถามนี้ในกล่องทราย)
John Dvorak

@JanDvorak นั่นสร้างสถานการณ์ที่การประมวลผลโค้ดหยุดทำงานเนื่องจากเธรดหนึ่งกำลังรอบางสิ่งที่ไม่สามารถรับได้ (เนื่องจากอีกเธรดกำลังค้างอยู่และรอให้เธรดแรกมี) หรือมันเป็นหนึ่งเธรด หากคุณสามารถสร้างสถานการณ์ดังกล่าวได้ด้วยหนึ่งเธรดแล้วก็ถือว่าใช้ได้ อ่านบทความ wikepedia เกี่ยวกับการหยุดชะงักนั่นคือสิ่งที่ฉันคาดหวัง
Justin

2
Code execution must haltฉันไม่เข้าใจ มันจะหยุดชะงักได้อย่างไรถ้ามันหยุด? คุณหมายถึงว่ามันจะรออะไรบางอย่างมากกว่าแค่การล็อคเหมือนไอ้งั้นเหรอ?
Cruncher

@Cruncher ลองดูตัวอย่างที่ใช้ไม่ได้ การเรียกใช้รหัสไม่หยุดเนื่องจากการวนซ้ำยังคงทำงานอยู่ ใช่ฉันหมายถึงรอแทนที่จะหมุน
Justin

ดังนั้นหากคุณสามารถทำให้เธรด UI รอตัวเองใน Dyalog APL นั่นหมายความว่ามีความหวังที่จะได้รับคำตอบจากจาวาสคริปต์หรือไม่? แม้ว่าฉันสงสัยว่าการคิดแบบนั้นจะเปิดประตูให้กับคำตอบนี้: Javascript 6 "wait ()" ... ermmm ไม่ ที่เกี่ยวข้อง: เป็นไปได้ไหมที่เธรดจะหยุดชะงักเอง?
นาธานคูเปอร์

คำตอบ:


11

Dyalog APL (10)

⎕TSYNC⎕TID

⎕TSYNCทำให้เธรดรอจนกว่าเธรดที่กำหนดจะ ⎕TIDให้เธรดปัจจุบัน

Dyalog APL สามารถจดจำการหยุดชะงักได้ดังนั้นจึงตอบสนองทันที

DEADLOCK

สิ่งที่สนุกคือคุณไม่จำเป็นต้องวางไข่เธรดพิเศษใด ๆ อีกต่อไปการทำเธรด UI รอให้ตัวเองนั้นมีมากมาย

หากนี่คือการโกงและจำเป็นต้องมีเธรดใหม่คุณสามารถทำได้ 27 ตัวอักษร:

{∇⎕TSYNC&{⎕TSYNC⎕TID+1}&0}0

F & xรันFในเธรดใหม่ตามค่าxและส่งคืน ID เธรด ดังนั้น:

  • {⎕TSYNC⎕TID+1}&0 สร้างเธรดที่จะซิงโครไนซ์กับเธรดที่มี ID สูงกว่าของตนเอง
  • ⎕TSYNC& สร้างเธรดใหม่ที่จะซิงโครไนซ์กับเธรดก่อนหน้าและที่ได้รับ ID หนึ่งสูงกว่าเธรดที่เพิ่งสร้าง (สมมติว่าไม่มีสิ่งอื่นใดกำลังสร้างเธรด)
  • ทำให้เกิดการวนซ้ำไม่สิ้นสุด (ดังนั้นเราจึงสร้างเธรดจนกว่าจะมีการหยุดชะงัก)

สิ่งนี้จะหยุดชะงักทันทีที่มีการสร้างเธรดที่สองก่อนที่เธรดแรกจะเริ่มทำงานซึ่งจะเกิดขึ้นเร็ว ๆ นี้:

9:DEADLOCK

บันทึก 2 ไบต์ด้วย: ⎕TID`คือ⎕TSYNC 0'. 0
Adám

8

ไปที่ 42

package main
func main(){<-make(chan int)}

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


2
มันทำงานยังไง?
จัสติน

4

Ruby, 39 ตัวอักษร

T=Thread;t=T.current;T.new{t.join}.join

ความคิดที่จะใช้ข้ามเข้าร่วมขโมยลงคอจากโยฮันเน Kuhn คำตอบของ

เราสามารถโกนอักขระสี่ตัว (เพิ่มเป็น35 ) ถ้าเราปรับรหัสให้เหมาะกับสภาพแวดล้อมที่เฉพาะเจาะจง IRB ของ JRuby นั้นเป็นแบบเธรดเดี่ยว:

T=Thread;T.new{T.list[0].join}.join


นี่คือโซลูชันก่อนหน้าของฉัน:

การทำให้เธรดติดอยู่บน mutex นั้นง่าย:

m=Mutex.new;2.times{Thread.new{m.lock}}

แต่นี่ไม่ใช่การหยุดชะงักที่เหมาะสมเนื่องจากเธรดที่สองไม่สามารถรอเธรดแรกได้ในทางเทคนิค "hold and wait" เป็นเงื่อนไขที่จำเป็นสำหรับการหยุดชะงักตาม Wikipedia เธรดแรกไม่รอและเธรดที่สองไม่เก็บสิ่งใดไว้

ทับทิม, 97 95 ตัวอักษร

m,n=Mutex.new,Mutex.new
2.times{o,p=(m,n=n,m)
Thread.new{loop{o.synchronize{p.synchronize{}}}}}

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

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

ทับทิม, 87 85 ตัวอักษร

m,n=Mutex.new,Mutex.new
loop{o,p=(m,n=n,m)
Thread.new{o.synchronize{p.synchronize{}}}}

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


ทำไมคุณถึงต้องการตัวแปรo& พิเศษp? คุณไม่สามารถเพียงแค่ผ่านmและnสำหรับกระทู้ใหม่ได้หรือไม่
Johannes Kuhn

@JohannesKuhn mและnเป็นระดับโลก ทั้งสองเธรดจะเห็นพวกเขาตามลำดับเดียวกัน oและpเป็นเธรดโลคัล (กำหนดขอบเขตการวนซ้ำ) การใช้t[...]อาจมีราคาแพงและฉันไม่เห็นวิธีที่ดีกว่าในการส่งพารามิเตอร์ไปยังเธรดมากกว่าผ่านการปิด การเพิ่มพารามิเตอร์พิเศษเพื่อnewทำให้รหัสยาวขึ้นด้วยสองตัวอักษร
John Dvorak

@JohannesKuhn ฉันหวังว่าคุณจะไม่รังเกียจฉันได้ยืมบางส่วนของตรรกะของคุณ
John Dvorak

ฉันไม่รังเกียจ ทำได้ดีมาก
Johannes Kuhn

หากเราสมมติว่าเราอยู่ในหัวข้อหลักเราสามารถโกนให้เหลือ 32 ตัวอักษรด้วยT=Thread;T.new{T.main.join}.join
histocrat

4

Python 46

import threading as t
t.Semaphore(0).acquire()

(การหยุดชะงักด้วยตนเอง)


1
from threading import* Semaphore(0).acquire()ฉันสั้นลงหนึ่งไบต์ฉันคิดว่า
Roman Gräf

@ Roman Yeah มันสั้นกว่าและใช้งานได้ tio.run/##K6gsycjPM/r/…
mbomb007

4

Bash + GNU coreutils 11 ไบต์

mkfifo x;<x

สร้าง FIFO จรจัดxในไดเรกทอรีปัจจุบัน (ดังนั้นคุณจะต้องไม่มีไฟล์ตามชื่อนั้น) FIFO สามารถลบได้ในลักษณะเดียวกับไฟล์ทั่วไปดังนั้นจึงไม่ยากที่จะลบทิ้ง

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

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

ลองคิดดูสิฉันสามารถนึกสถานการณ์สามอย่างที่คล้ายกับการหยุดชะงัก:

  1. การหยุดชะงักแบบ "ดั้งเดิม": สองเธรดแต่ละตัวรอการปลดล็อคที่จะถูกยึดโดยเธรดอื่น

  2. เธรดเดี่ยวกำลังรอการปลดล็อก แต่มันจะล็อกตัวเอง (และจะบล็อกตัวเองไม่ให้สามารถปลดล็อกได้)

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

นี่คือการหยุดชะงักของประเภท 3 ซึ่งเป็นวิธีที่แตกต่างจากสองส่วนพื้นฐาน: ในทางทฤษฎีคุณสามารถเขียนโปรแกรมเพื่อปลดล็อคการซิงโครไนซ์ดั้งเดิมในคำถามแล้วเรียกใช้ ที่กล่าวไว้เช่นเดียวกับการหยุดชะงักของประเภท 1 และ 2 เนื่องจากมีหลายภาษาที่อนุญาตให้คุณปลดล็อคที่คุณไม่ได้เป็นเจ้าของ (คุณไม่ควรและจะไม่มีเหตุผลที่จะทำเช่นนั้นหากคุณมีเหตุผลที่จะ ใช้ตัวล็อคในตอนแรก แต่ใช้งานได้…) นอกจากนี้ยังมีมูลค่าการพิจารณาโปรแกรมเช่นmkfifo x;<x;echo test>x; โปรแกรมเรียงลำดับตรงข้ามของการหยุดชะงักประเภท 2 (พยายามเปิดปลายทั้งสองของ FIFO แต่ไม่สามารถเปิดปลายด้านหนึ่งจนกระทั่งหลังจากเปิดปลายอีกด้านหนึ่ง) แต่มันทำจากอันนี้โดยการเพิ่มส่วนเสริม รหัสที่ไม่เคยทำงานหลังจากนี้! ฉันเดาว่าปัญหาคือว่าการล็อกถูกล็อกหรือไม่นั้นขึ้นอยู่กับความตั้งใจในการใช้ล็อคดังนั้นจึงยากที่จะกำหนดอย่างเป็นกลาง (โดยเฉพาะอย่างยิ่งในกรณีเช่นนี้ที่จุดประสงค์เดียวของล็อคคือการผลิตเด๊ดล็อกโดยเจตนา )



2

ทุบตีด้วย glibc, 6 ไบต์

ขออภัยที่จะชุบชีวิตด้ายเก่า แต่ไม่สามารถต้านทานได้

ในฐานะที่เป็นราก:

pldd 1

จากมนุษย์ pldd :

BUGS
ตั้งแต่ glibc 2.19, pldd เสีย: มันแค่แฮงค์เมื่อเรียกใช้งาน มันไม่ชัดเจนว่ามันจะได้รับการแก้ไข


ไม่มีปัญหากับการตอบรับบนดอกยางเก่าตราบใดที่ต้นฉบับไม่ไวต่อเวลา
Ad Hoc Garf Hunter

2

Java, 191

class B extends Thread{public static void main(String[]a)throws Exception{new B().join();}Thread d;B(){d=Thread.currentThread();start();}public void run(){try{d.join();}catch(Exception e){}}}

Ungolfed:

class B extends Thread {
    Thread d;
    public static void main(String[] args) throws Exception {
        new B().join();
    }
    B() { // constructor
        d = Thread.currentThread();
        start();
    }
    public void run() {
        try {
            d.join();
        } catch (Exception e) {
        }
    }
}

เริ่มเธรดใหม่และดำเนินการjoinต่อ (รอจนกว่าเธรดนี้จะเสร็จสิ้น) ในขณะที่เธรดใหม่จะทำเช่นเดียวกันกับเธรดดั้งเดิม


คุณช่วยทำให้สั้นลงได้โดยการขว้างและจับErrorแทนการจับได้Exceptionหรือไม่?
mbomb007

Nope Thread.join()พ่นInteruptedExceptionซึ่งไม่ได้เป็น subclass Errorของ
Johannes Kuhn

2

Tcl, 76

package r Thread;thread::send [thread::create] "thread::send [thread::id] a"

การหยุดชะงัก

สิ่งนี้จะสร้างเธรดใหม่และบอกให้เธรดอื่นส่งข้อความถึงเธรดของฉัน (สคริปต์เพื่อดำเนินการ)

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


มันทำงานอย่างไร
John Dvorak

thread::sendจัดคิวสคริปต์ที่ควรดำเนินการในเธรดอื่นและรอให้เสร็จสมบูรณ์ ดังนั้นในตอนท้ายเราจึงมีเธรด 1 รอเธรด 2 และเธรด 2 รอเธรด 1
โยฮันเนสคูห์น

1

ทางเลือก Java พร้อมจอมอนิเตอร์ไม่เหมาะสม (248 ตัวอักษร)

class A{public static void main(String args[]) throws Exception{final String a="",b="";new Thread(new Runnable(){public void run(){try {synchronized(b){b.wait();}} catch (Exception e) {}a.notify();}}).start();synchronized(a){a.wait();}b.notify();}}

1

สกาลา 104 ไบต์

class A{s=>lazy val x={val t=new Thread{override def run{s.synchronized{}}};t.start;t.join;1}};new A().x

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


มันทำงานอย่างไร
Addison Crump

ฉันเพิ่มคำอธิบาย
Martin Seeler

1

Kotlin ขนาด 35/37/55 ไบต์

หัวข้อทั่วไป: Thread.currentThread().join().

ไม่รวมข้อบกพร่องของ JVM / รหัสพิเศษสำหรับการส่งนี้ shoud นี้จะไม่ส่งคืนเนื่องจากเธรดการเรียกใช้ปัจจุบันถูกปิดใช้งานเพื่อรอตัวเองตาย


คุณสมบัติความชั่วร้าย: 35 ไบต์ (ไม่แข่งขัน): 35 ไบต์

val a=Thread.currentThread().join()

การประกาศสิ่งนี้เป็นที่ที่ถูกต้องจะทำให้เกิดการหยุดชะงัก ในกรณีของคุณสมบัติระดับบนสุดนั่นจะเป็นตัวโหลดคลาสเริ่มต้นคลาส JVM ที่แม็พสำหรับไฟล์นั้น (โดยดีฟอลต์[file name]Kt.class)

การไม่แข่งขันเพราะ "ทำให้สิ่งนี้เป็นสถานที่ให้บริการระดับบนสุดได้ทุกที่" เป็นข้อ จำกัด


ฟังก์ชั่น: 37 ไบต์

fun a()=Thread.currentThread().join()


main (): 55 bytes

fun main(a:Array<String>)=Thread.currentThread().join()


1

PowerShell, 36 28 23 ไบต์

gps|%{$_.waitforexit()}

การหยุดชะงักตัวเอง เราได้รับกระบวนการทั้งหมดGet-Processแล้วรออย่างอดทนเพื่อให้แต่ละคนออกจาก ... ซึ่งจะไม่เกิดขึ้นโดยประมาณเนื่องจากกระบวนการจะต้องรอตัวเอง

แก้ไข - บันทึก 5 ไบต์ด้วย Roman Gräf


(gps)|%{$_.waitforexit()}สั้นลงสามไบต์และรอให้กระบวนการทั้งหมดจบการทำงาน
Roman Gräf

@ RomanGräfแน่นอน แต่ไม่จำเป็นต้องมี parens รอบ ๆgpsในกรณีนี้ดังนั้นบันทึกทั้งหมด 5 ไบต์
AdmBorkBork

0

C (Linux เท่านั้น), 31 Bytes - ลองออนไลน์!

main(a){syscall(240,&a,0,a,0);}

การเรียกระบบ 240 (0xf0) คือfutex (2)หรือ mutex userspace ที่รวดเร็ว เอกสารระบุว่าอาร์กิวเมนต์แรกคือตัวชี้ไปยัง futex อาร์กิวเมนต์ที่สองคือการดำเนินการ (0 หมายถึง FUTEX_WAIT นั่นคือรอจนกว่าเธรดอื่นจะปลดล็อค futex) อาร์กิวเมนต์ที่สามคือค่าที่คุณคาดว่า futex จะมีในขณะที่ยังล็อคอยู่และที่สี่คือตัวชี้ไปยังการหมดเวลา (NULL โดยไม่มีการหมดเวลา)

เห็นได้ชัดว่าไม่มีหัวข้ออื่น ๆ ที่จะปลดล็อค futex มันจะรอตลอดไปในการหยุดชะงักด้วยตนเอง สามารถตรวจสอบได้ (ผ่านทางtopหรือตัวจัดการงานอื่น ๆ ) ว่ากระบวนการไม่ใช้เวลา CPU ตามที่เราควรคาดหวังจากเธรดที่ไม่มีการล็อค


0

Julia 0.6 , 13 ไบต์

ภาษาใหม่กว่าคำถาม รองาน (เช่นงานประจำในปัจจุบันจะอยู่ในเธรดเดียวกันในรุ่นอนาคตของ Julia อาจอยู่ในเธรดอื่น) ซึ่งไม่ได้กำหนดเวลาให้ทำงาน

wait(@task +)

ลองออนไลน์!


0

BotEngine, 3x3 = 9 (9 ไบต์)

v
lCv
> <

ขั้นตอนที่ 5 จบลงด้วยบอตสองตัวที่รอกันและกันเพื่อย้าย ... บอบ็อตหนึ่งไม่สามารถขยับได้เพราะรอให้อีกอันย้ายออกจากจตุรัสล่างขวาและบ็อตอีกอันไม่สามารถขยับได้เพราะรอ บอตแรกที่จะย้ายออกจากจัตุรัสกลางด้านล่าง


0

โมเดลน้ำตก (Ratiofall) ขนาด 13 ไบต์

[[2,1],[1,1]]

ลองออนไลน์!

ต่อไปนี้เป็นคำตอบแบบไม่สนุก นี่เป็นลูปที่ไม่มีที่สิ้นสุดที่ง่ายที่สุดที่เป็นไปได้ใน The Waterfall Model (ตัวแปรใน The Waterfall Model จะลดลงซ้ำ ๆ เมื่อใดก็ตามที่ไม่มีสิ่งใดเกิดขึ้นโปรแกรมนี้จะกำหนดตัวแปรที่เพิ่มขึ้นเองเมื่อใดก็ตามที่มันลดลง

คำถามถามถึงการหยุดชะงักไม่ใช่ห่วงที่ไม่สิ้นสุด อย่างไรก็ตามเราสามารถใช้ประโยชน์จากพฤติกรรมของการใช้งานเฉพาะ ที่การปรับให้เหมาะสมระดับ 2 หรือสูงกว่า (ค่าเริ่มต้นคือ 3) ล่าม Ratiofall จะตรวจจับลูปไม่สิ้นสุดและปรับให้เหมาะสม ... เป็นการหยุดชะงัก! ดังนั้นอย่างน้อยถ้าเราพิจารณาภาษาที่จะถูกกำหนดโดยการนำไปใช้ (ซึ่งโดยปกติจะเป็นกรณีของเว็บไซต์นี้) โปรแกรมนี้จะทำการกำหนด deadlock จริง ๆ แทนที่จะเป็นลูปที่ไม่มีที่สิ้นสุด

คุณสามารถเห็นหลักฐานการหยุดชะงักได้จากรายงานเวลาในการลองออนไลน์! ลิงค์ด้านบน:

Real time: 60.004 s
User time: 0.006 s
Sys. time: 0.003 s
CPU share: 0.01 %
Exit code: 124

โปรแกรมรันเป็นเวลา 60 วินาที (จนกระทั่ง TIO ถูกยกเลิกโดยอัตโนมัติ) แต่ส่วนใหญ่แล้วนั้นไม่มีการใช้งาน CPU ไม่มีการใช้เวลาโดยโปรแกรมที่กำลังทำงานและเคอร์เนลในนามของโปรแกรมไม่มีเวลา

เพื่อให้ได้หลักฐานที่แข็งแกร่งยิ่งขึ้นคุณสามารถเรียกใช้ Ratiofall ภายใต้ดีบักเกอร์ระดับระบบที่เรียกเช่นstrace; การทำเช่นนี้บน Linux จะแสดงการปิดกั้นล่ามในการfutexโทรของระบบที่พยายามล็อคซึ่งจะไม่ถูกปล่อยออกมา


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