เหตุใด JavaScript จึงไม่รองรับมัลติเธรด


269

มันเป็นการตัดสินใจโดยเจตนาหรือมีปัญหากับเบราว์เซอร์วันปัจจุบันของเราซึ่งจะแก้ไขในเวอร์ชันที่จะมาถึงหรือไม่?


3
ดูเพิ่มเติมที่คำตอบสำหรับคำถามJavaScript และเธรดสำหรับข้อมูลเกี่ยวกับเว็บแรงงาน / เธรดผู้ปฏิบัติงาน
Sam Hasler

115
สวัสดีเพื่อนชาว Google คุณอาจสังเกตเห็นว่าทุกอย่างที่นี่ดูเหมือนจะค่อนข้างล้าสมัย (หมายเหตุคำถามนี้ถูกถามเมื่อกว่า 5 ปีที่แล้ว) เนื่องจากมีการถามเว็บเบราว์เซอร์ได้รับความสามารถบางอย่างที่เท่าที่ฉันสามารถบอกได้ ลองดูที่ Web Workers: msdn.microsoft.com/en-us/hh549259.aspx
ArtOfWarfare

2
Multithread.jsล้อม Web Workers และช่วยให้ง่ายในการมัลติเธรดใน JS ใช้งานได้กับเบราว์เซอร์ใหม่ทั้งหมดรวมถึง iOS Safari :)
หน่วย

คำตอบ:


194

JavaScript ไม่รองรับมัลติเธรดเนื่องจากล่าม JavaScript ในเบราว์เซอร์เป็นเธรดเดียว (AFAIK) แม้แต่ Google Chrome ก็ไม่ยอมให้จาวาสคริปต์ของเว็บเพจหนึ่ง ๆ ทำงานพร้อมกันเพราะจะทำให้เกิดปัญหาการทำงานพร้อมกันจำนวนมากในหน้าเว็บที่มีอยู่ Chrome ทั้งหมดทำแยกองค์ประกอบหลาย ๆ อย่าง (แท็บต่าง ๆ , ปลั๊กอิน, และอื่น ๆ ) เป็นกระบวนการแยกกัน แต่ฉันไม่สามารถจินตนาการหน้าเดียวที่มีมากกว่าหนึ่งเธรด JavaScript

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

เราใช้ไลบรารีนามธรรมใน JavaScript ที่ทำให้เราสามารถสร้างกระบวนการและเธรดที่จัดการทั้งหมดโดยล่าม JavaScript เดียวกัน สิ่งนี้ทำให้เราสามารถดำเนินการในลักษณะต่อไปนี้:

  • กระบวนการ A, เธรด 1
  • กระบวนการ A, เธรด 2
  • กระบวนการ B, เธรด 1
  • กระบวนการ A, เธรด 3
  • กระบวนการ A, เธรด 4
  • กระบวนการ B, เธรด 2
  • หยุดกระบวนการชั่วคราว
  • กระบวนการ B, เธรด 3
  • กระบวนการ B, เธรด 4
  • กระบวนการ B, เธรด 5
  • เริ่มกระบวนการ A
  • กระบวนการ A, เธรด 5

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

สำหรับอนาคตของ JavaScript ให้ตรวจสอบสิ่งนี้: https://developer.mozilla.org/presentations/xtech2006/javascript/


73
ฉันคิดว่าไม่เคยนำมาใช้เป็นวิสัยทัศน์ที่แคบเกินไป ฉันรับประกันเว็บแอปในที่สุดจะสามารถเป็นมัลติเธรดอย่างแท้จริง (มันเป็นตรรกะเท่านั้นเมื่อเว็บแอปโดดเด่นมากขึ้นและฮาร์ดแวร์จะขนานกันมากขึ้น) และอย่างที่ฉันเห็นเนื่องจาก JavaScript เป็นภาษาที่พัฒนาอย่างแท้จริงของเว็บ ในที่สุดจะต้องรองรับมัลติเธรดหรือถูกแทนที่ด้วยบางสิ่งที่ทำ
devios1

6
ไม่น่าจะเป็นคำพูดที่กล้าหาญเกินไป :) แต่ฉันยังคงคิดว่าข้อดีของจาวาสคริปต์ที่แท้จริงแบบมัลติเธรดไม่เป็นไปได้ในอนาคตอันใกล้นี้;)
Kamiel Wanrooij

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

ความยากลำบากนั้นมีขนาดใหญ่กว่าความเป็นไปได้พิเศษที่ฉันไม่แน่ใจว่าถ้าคุณคิดถึงความเป็นไปได้พิเศษทั้งหมด ฉันกำลังคิดถึงกรณีที่มีการใช้ webgl เช่นในเกมหรือการสร้างภาพกราฟิก เช่นพิจารณา Google แผนที่เวอร์ชัน 3 มิติใหม่ ในเมืองที่มีแบบจำลอง 3 มิติจำนวนมากพีซีของฉันต้องใช้เวลาประมาณ 2 นาทีในการโหลดทุกอย่างเมื่อต้องสร้างบ้านหลายหลัง ในบางมุมทั้งกราฟิกการ์ดและเครือข่ายของฉันกำลังทำงานอย่างเต็มประสิทธิภาพ แต่โปรเซสเซอร์ 1 ใน 8 ตัวอยู่ที่ 100% การมัลติเธรดเป็นเรื่องที่น่ากังวลอย่างมากในแง่ของ fps ตามตัวอย่างนี้: youtube.com/watch?v=sJ2p982cZFc
Scindix

25

JavaScript หลายเธรด (มีข้อ จำกัด ) อยู่ที่นี่ Google นำคนงานมาใช้สำหรับ Gears และคนงานกำลังรวมอยู่ใน HTML5 เบราว์เซอร์ส่วนใหญ่ได้เพิ่มการรองรับคุณสมบัตินี้แล้ว

มีการรับประกันความปลอดภัยของข้อมูลของเธรดเนื่องจากข้อมูลทั้งหมดที่สื่อสารไปยัง / จากผู้ปฏิบัติงานได้รับการจัดลำดับ / คัดลอก

สำหรับข้อมูลเพิ่มเติมอ่าน:

http://www.whatwg.org/specs/web-workers/current-work/

http://ejohn.org/blog/web-workers/


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

1
@beefeather นั่นเป็นเรื่องจริง มันเป็นวิธีการเพิ่มเติม
Neil

23

ตามเนื้อผ้า JS มีไว้สำหรับโค้ดสั้น ๆ ที่รันเร็ว หากคุณมีการคำนวณที่สำคัญเกิดขึ้นคุณทำมันบนเซิร์ฟเวอร์ - ความคิดของแอป JS + HTML ที่รันในเบราว์เซอร์ของคุณเป็นเวลานานในการทำสิ่งที่ไม่ใช่เรื่องไร้สาระเป็นเรื่องไร้สาระ

แน่นอนว่าตอนนี้เรามีสิ่งนั้นแล้ว แต่จะต้องใช้เวลาสักหน่อยก่อนที่เบราว์เซอร์จะทันซึ่งส่วนใหญ่ได้รับการออกแบบรอบ ๆ แบบเธรดเดียวและการเปลี่ยนแปลงที่ไม่ง่าย Google Gears มีปัญหาที่อาจเกิดขึ้นจำนวนมากโดยกำหนดให้การดำเนินการพื้นหลังนั้นแยก - ไม่มีการเปลี่ยนแปลง DOM (เนื่องจากไม่ปลอดภัยกับเธรด) ไม่มีการเข้าถึงวัตถุที่สร้างโดยเธรดหลัก (เหมือนกัน) ในขณะที่ข้อ จำกัด นี้น่าจะเป็นการออกแบบที่ใช้ประโยชน์ได้มากที่สุดในอนาคตอันใกล้ทั้งสองอย่างเพราะมันช่วยให้การออกแบบของเบราว์เซอร์ง่ายขึ้นและเพราะช่วยลดความเสี่ยงที่เกี่ยวข้องกับการอนุญาตให้ JS coders ที่ไม่มีประสบการณ์

@marcio :

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

ดังนั้นอย่าให้พวกเขามีเครื่องมือที่ง่ายต่อการใช้ผิดวัตถุประสงค์ที่เว็บไซต์อื่น ๆ ที่ฉันเปิดทุกแห่งจะล่มเบราว์เซอร์ของฉัน การใช้ความไร้เดียงสาของสิ่งนี้จะนำคุณเข้าสู่ดินแดนที่ทำให้ MS ปวดหัวจำนวนมากในระหว่างการพัฒนา IE7: ผู้เขียน add-on ที่เล่นอย่างรวดเร็วและหลวมด้วยรูปแบบการทำเกลียวทำให้เกิดข้อบกพร่องที่ซ่อนอยู่ . BAD หากคุณกำลังเขียนโปรแกรมเสริม ActiveX แบบหลายเธรดสำหรับ IE ฉันคิดว่ามันมาพร้อมกับอาณาเขต; ไม่ได้หมายความว่ามันจะต้องไปไกลกว่านั้นอีก


6
"จะช่วยลดความเสี่ยงที่เกี่ยวข้อง> ในการอนุญาตให้โปรแกรมเข้ารหัส JS ที่ไม่มีประสบการณ์> ยุ่งเกี่ยวกับเธรด" เหตุใดจึงเป็นเหตุผลที่ไม่ใช้มัลติเธรดใน Javascript โปรแกรมเมอร์สามารถทำสิ่งที่พวกเขาต้องการด้วยเครื่องมือที่พวกเขามี หากดีหรือไม่ดีก็เป็นปัญหาของพวกเขา ด้วยโมเดลกระบวนการของ Google Chrome จะไม่สามารถส่งผลกระทบต่อแอปพลิเคชันอื่น ๆ :)
Marcio Aguiar

3
@ Shog9 - "อย่าให้เครื่องมือ [โปรแกรมเมอร์] ที่ง่ายต่อการใช้งานผิด ๆ ที่เว็บไซต์อื่น ๆ ที่ฉันเปิดท้ายจะพังเบราว์เซอร์" - อะไร? ด้วยเหตุผลเดียวกันนั้นไม่ควรใช้ภาษาแบบมัลติเธรดเพราะหากพวกเขาเสนอว่าโปรแกรมอื่น ๆ ที่คุณพยายามเปิดจะมีปัญหา ยกเว้นว่ามันจะไม่ทำงานอย่างนั้น การทำเกลียวมีอยู่ในภาษาส่วนใหญ่และโปรแกรมเมอร์มือใหม่ส่วนใหญ่ไม่ได้สัมผัสและส่วนใหญ่ที่ไม่ได้ใช้งานจริงและแอพที่ไม่เคยได้รับความนิยมหรือใช้กันอย่างแพร่หลาย
ArtOfWarfare

11

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

เพียงให้ฟังก์ชั่นของคุณทำงานเล็กน้อยจากนั้นโทรหาสิ่งที่ชอบ:

setTimeout(function () {
    ... do the rest of the work...
}, 0);

และสิ่งอื่น ๆ ที่ต้องทำ (เช่นอัปเดต UI, ภาพเคลื่อนไหว, ฯลฯ ) จะเกิดขึ้นเมื่อพวกเขามีโอกาส


กรณีส่วนใหญ่ฉันต้องการใช้loopภายในsetTimeoutแต่เห็นได้ชัดว่าไม่ได้ผล คุณทำอะไรแบบนั้นหรือว่าคุณแฮ็ค? ตัวอย่างจะเป็นสำหรับอาร์เรย์ 1000 องค์ประกอบผมคาดว่าจะใช้สองสำหรับลูปภายในสองsetTimeoutสายดังกล่าวว่าลูปแรกผ่านและพิมพ์องค์ประกอบลูปที่สองผ่านและบรรยากาศการพิมพ์0..499 500..999
benjaminz

โดยปกติแล้วเทคนิคคือการบันทึกสถานะและดำเนินการต่อ ตัวอย่างเช่นสมมติว่าคุณต้องการพิมพ์ 0 ถึง 1,000 คุณอาจพิมพ์ 0 ถึง 499 จากนั้นทำเคล็ดลับ setTimeout ด้วยอาร์กิวเมนต์ 500 รหัสภายในจะรู้ว่าจะใช้อาร์กิวเมนต์ (500) และเริ่มวนรอบจากที่นั่น
Eyal

8

คุณหมายถึงเพราะเหตุใดภาษาจึงไม่รองรับมัลติเธรดหรือทำไมเอนจิน JavaScript ในเบราว์เซอร์ไม่สนับสนุนการมัลติเธรด?

คำตอบสำหรับคำถามแรกคือ JavaScript ในเบราว์เซอร์นั้นมีวัตถุประสงค์เพื่อให้ทำงานในแซนด์บ็อกซ์และในวิธีที่เป็นอิสระจากเครื่อง / ระบบปฏิบัติการการเพิ่มการรองรับมัลติเธรดจะทำให้ภาษาซับซ้อนและผูกกับภาษามากเกินไป


6

Node.js 10.5+ รองรับเธรดผู้ปฏิบัติงานเป็นคุณลักษณะทดลอง (คุณสามารถใช้กับการเปิดใช้งาน--experimental- flag flag): https://nodejs.org/api/worker_threads.html

ดังนั้นกฎคือ:

  • ถ้าคุณต้องการที่จะทำI / O ผูกพัน opsแล้วใช้กลไกภายใน (หรือที่เรียกว่า callback / contract / async-await)
  • หากคุณจำเป็นต้องทำCPU ผูก opsจากนั้นใช้หัวข้อผู้ปฏิบัติงาน

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

มิฉะนั้นถ้าคุณจำเป็นต้องเรียกใช้งานโหลดซีพียูจำนวนมากด้วยฟังก์ชั่นที่ไม่ระบุชื่อคุณสามารถไปที่https://github.com/wilk/microjobไลบรารีขนาดเล็กที่สร้างขึ้นรอบเธรดผู้ทำงาน


4

เช่นเดียวกับด้าน b กล่าวว่าคำถามไม่ชัดเจน สมมติว่าคุณกำลังถามเกี่ยวกับการสนับสนุนมัลติเธรดในภาษา: เนื่องจากมันไม่จำเป็นสำหรับ 99.999% ของแอปพลิเคชันที่ทำงานในเบราว์เซอร์ในปัจจุบัน หากคุณต้องการจริงๆมีวิธีแก้ไขปัญหา (เช่นใช้ window.setTimeout)

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


3

Intel ได้ทำการวิจัยโอเพนซอร์ซเกี่ยวกับการทำมัลติเธรดใน Javascript เมื่อเร็ว ๆ นี้ซึ่งได้เปิดตัวใน GDC 2012 นี่คือลิงค์สำหรับวิดีโอวิดีโอกลุ่มวิจัยใช้ OpenCL ซึ่งเน้นไปที่ชุด Intel Chip และ Windows OS เป็นหลัก โครงการมีชื่อรหัสว่า RiverTrail และรหัสนั้นมีอยู่ใน GitHub

ลิงค์ที่มีประโยชน์บางอย่าง:

สร้าง Highway Computing สำหรับเว็บแอปพลิเคชัน


2

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


1

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

เบราว์เซอร์ใหม่ที่ Google คาดว่าจะเปิดตัวในวันนี้ (Google Chrome) รันโค้ดบางส่วนพร้อมกันโดยแยกออกเป็นกระบวนการ

แน่นอนว่าภาษาแกนกลางสามารถให้การสนับสนุนเหมือนกับพูด Java แต่การสนับสนุนบางอย่างเช่นการทำงานพร้อมกันของ Erlang นั้นไม่มีที่ไหนใกล้ขอบฟ้า


1

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


0

เท่าที่ฉันได้ยิน Google Chrome จะมีจาวาสคริปต์แบบมัลติเธรดดังนั้นจึงเป็นปัญหา "การใช้งานปัจจุบัน"


0

หากไม่มีการสนับสนุนภาษาที่เหมาะสมสำหรับการทำข้อมูลให้ตรงกันของเธรดจะไม่ทำให้การใช้งานใหม่ลองดู แอป JS ที่ซับซ้อนที่มีอยู่ (เช่นสิ่งใดก็ตามที่ใช้ ExtJS) อาจเกิดปัญหาได้โดยไม่คาดคิด แต่หากไม่มีsynchronizedคำหลักหรือสิ่งที่คล้ายกันก็จะยากมากหรือเป็นไปไม่ได้ที่จะเขียนโปรแกรมใหม่ที่ทำงานอย่างถูกต้อง


-1

อย่างไรก็ตามคุณสามารถใช้ฟังก์ชั่น eval เพื่อให้เกิดภาวะพร้อมกันในบางพื้นที่

/* content of the threads to be run */
var threads = [
        [
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');",
            "document.write('Foo <br/>');"
        ],
        [
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');",
            "document.write('Bar <br/>');"
        ]
    ];

window.onload = function() {
    var lines = 0, quantum = 3, max = 0;

    /* get the longer thread length */
    for(var i=0; i<threads.length; i++) {
        if(max < threads[i].length) {
            max = threads[i].length;
        }
    }

    /* execute them */
    while(lines < max) {
        for(var i=0; i<threads.length; i++) {
            for(var j = lines; j < threads[i].length && j < (lines + quantum); j++) {
                eval(threads[i][j]);
            }
        }
        lines += quantum;
    }
}

-2

Multi-Threading ด้วย javascript นั้นชัดเจนโดยใช้ webworkers ที่นำโดย HTML5

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

มีเฟรมเวิร์กจำนวนมากที่อนุญาตให้สร้างโครงสร้างการเขียนโปรแกรมระหว่างเธรดซึ่งในหมู่พวกเขา OODK-JS เป็นเฟรมเวิร์ก OOP js ที่สนับสนุนการเขียนโปรแกรมพร้อมกัน https://github.com/GOMServices/oodk-js-oop-for-js


5
การแชร์หน่วยความจำคือคำจำกัดความที่แน่นอนของเธรดซึ่งตรงข้ามกับกระบวนการที่แยกต่างหาก (เช่น fork () vs exec ()) เธรดสามารถแชร์อ็อบเจ็กต์กระบวนการต้องใช้ IPC Web Workers ไม่ได้มีหลายเธรด
felixfbecker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.