Multi-Threading
Base.Threads.@threads — MacroThreads.@threads [schedule] for ... endماكرو لتنفيذ حلقة for بالتوازي. يتم توزيع مساحة التكرار على مهام ذات حبيبات خشنة. يمكن تحديد هذه السياسة بواسطة وسيط schedule. ينتظر تنفيذ الحلقة تقييم جميع التكرارات.
انظر أيضًا: @spawn و pmap في Distributed.
مساعدة موسعة
الدلالات
ما لم يتم تحديد ضمانات أقوى بواسطة خيار الجدولة، فإن الحلقة التي يتم تنفيذها بواسطة ماكرو @threads لها الدلالات التالية.
تنفذ ماكرو @threads جسم الحلقة بترتيب غير محدد وبتزامن محتمل. لا تحدد التعيينات الدقيقة للمهام وخيوط العمل. يمكن أن تكون التعيينات مختلفة في كل تنفيذ. يجب ألا تفترض شفرة جسم الحلقة (بما في ذلك أي شفرة يتم استدعاؤها بشكل غير مباشر منها) أي افتراضات حول توزيع التكرارات على المهام أو خيط العمل الذي يتم تنفيذها فيه. يجب أن يكون جسم الحلقة لكل تكرار قادرًا على إحراز تقدم مستقل عن التكرارات الأخرى وأن يكون خاليًا من سباقات البيانات. على هذا النحو، قد تؤدي التزامنات غير الصالحة عبر التكرارات إلى توقف بينما قد تؤدي الوصولات غير المتزامنة إلى سلوك غير محدد.
على سبيل المثال، تشير الشروط أعلاه إلى أن:
- يجب أن يتم تحرير القفل المأخوذ في تكرار داخل نفس التكرار.
- التواصل بين التكرارات باستخدام بدائل حظر مثل
Channels غير صحيح. - الكتابة فقط إلى المواقع غير المشتركة عبر التكرارات (ما لم يتم استخدام قفل أو عملية ذرية).
- ما لم يتم استخدام الجدول الزمني
:static، قد تتغير قيمةthreadid()حتى داخل تكرار واحد. انظرTask Migration.
المجدولون
بدون وسيط المجدول، فإن الجدولة الدقيقة غير محددة وتختلف عبر إصدارات جوليا. حاليًا، يتم استخدام :dynamic عندما لا يتم تحديد المجدول.
وسيط schedule متاح اعتبارًا من جوليا 1.5.
:dynamic (افتراضي)
ينفذ المجدول :dynamic التكرارات ديناميكيًا إلى خيوط العمل المتاحة. تفترض التنفيذ الحالي أن عبء العمل لكل تكرار متساوي. ومع ذلك، قد تتم إزالة هذا الافتراض في المستقبل.
تعتبر هذه الخيار الجدولي مجرد تلميح لآلية التنفيذ الأساسية. ومع ذلك، يمكن توقع بعض الخصائص. عدد Tasks المستخدمة بواسطة المجدول :dynamic محدود بعدد صغير مضاعف لعدد خيوط العمل المتاحة (Threads.threadpoolsize()). تعالج كل مهمة مناطق متجاورة من مساحة التكرار. وبالتالي، فإن @threads :dynamic for x in xs; f(x); end يكون عادةً أكثر كفاءة من @sync for x in xs; @spawn f(x); end إذا كانت length(xs) أكبر بكثير من عدد خيوط العمل ووقت تشغيل f(x) أصغر نسبيًا من تكلفة إنشاء وتزامن مهمة (عادة أقل من 10 ميكروثانية).
خيار :dynamic لوسيط schedule متاح وهو الافتراضي اعتبارًا من جوليا 1.8.
:greedy
يولد المجدول :greedy ما يصل إلى Threads.threadpoolsize() مهام، كل منها تعمل بشغف على القيم المتكررة المعطاة عند إنتاجها. بمجرد أن تنتهي مهمة واحدة من عملها، تأخذ القيمة التالية من المكرر. العمل الذي تقوم به أي مهمة فردية ليس بالضرورة على قيم متجاورة من المكرر. قد ينتج المكرر المعطى قيمًا إلى الأبد، فقط واجهة المكرر مطلوبة (لا يوجد فهرسة).
يعتبر هذا الخيار الجدولي عمومًا خيارًا جيدًا إذا كان عبء العمل للتكرارات الفردية غير متساوي/لديه انتشار كبير.
خيار :greedy لوسيط schedule متاح اعتبارًا من جوليا 1.11.
:static
ينشئ المجدول :static مهمة واحدة لكل خيط ويقسم التكرارات بالتساوي بينها، مع تعيين كل مهمة بشكل محدد لكل خيط. على وجه الخصوص، فإن قيمة threadid() مضمونة أن تكون ثابتة داخل تكرار واحد. يعتبر تحديد :static خطأ إذا تم استخدامه من داخل حلقة @threads أخرى أو من خيط غير 1.
يوجد جدولة :static لدعم انتقال الشفرة المكتوبة قبل جوليا 1.3. في وظائف المكتبة المكتوبة حديثًا، يتم تثبيط جدولة :static لأن الوظائف التي تستخدم هذا الخيار لا يمكن استدعاؤها من خيوط العمل العشوائية.
أمثلة
لتوضيح استراتيجيات الجدولة المختلفة، اعتبر الوظيفة التالية busywait التي تحتوي على حلقة مؤقتة غير متوقفة تعمل لعدد معين من الثواني.
julia> function busywait(seconds)
tstart = time_ns()
while (time_ns() - tstart) / 1e9 < seconds
end
end
julia> @time begin
Threads.@spawn busywait(5)
Threads.@threads :static for i in 1:Threads.threadpoolsize()
busywait(1)
end
end
6.003001 seconds (16.33 k allocations: 899.255 KiB, 0.25% compilation time)
julia> @time begin
Threads.@spawn busywait(5)
Threads.@threads :dynamic for i in 1:Threads.threadpoolsize()
busywait(1)
end
end
2.012056 seconds (16.05 k allocations: 883.919 KiB, 0.66% compilation time)مثال :dynamic يستغرق 2 ثانية حيث أن أحد خيوط العمل غير المشغولة قادر على تشغيل اثنين من التكرارات التي تستغرق 1 ثانية لإكمال حلقة for.
Base.Threads.foreach — FunctionThreads.foreach(f, channel::Channel;
schedule::Threads.AbstractSchedule=Threads.FairSchedule(),
ntasks=Threads.threadpoolsize())مماثل لـ foreach(f, channel)، ولكن يتم تقسيم التكرار عبر channel واستدعاءات f عبر ntasks مهام تم إنشاؤها بواسطة Threads.@spawn. ستنتظر هذه الدالة حتى تكتمل جميع المهام التي تم إنشاؤها داخليًا قبل أن تعود.
إذا كان schedule isa FairSchedule، ستحاول Threads.foreach إنشاء مهام بطريقة تمكن جدولة جوليا من توزيع عناصر العمل عبر الخيوط بشكل أكثر حرية. هذه الطريقة عمومًا لها تكلفة أعلى لكل عنصر، ولكن قد تؤدي بشكل أفضل من StaticSchedule بالتزامن مع أحمال عمل متعددة الخيوط الأخرى.
إذا كان schedule isa StaticSchedule، ستقوم Threads.foreach بإنشاء مهام بطريقة تتسبب في تكلفة أقل لكل عنصر من FairSchedule، ولكنها أقل ملاءمة لتوازن الحمل. وبالتالي، قد تكون هذه الطريقة أكثر ملاءمة للأحمال الدقيقة والمتجانسة، ولكن قد تؤدي بشكل أسوأ من FairSchedule بالتزامن مع أحمال عمل متعددة الخيوط الأخرى.
أمثلة
julia> n = 20
julia> c = Channel{Int}(ch -> foreach(i -> put!(ch, i), 1:n), 1)
julia> d = Channel{Int}(n) do ch
f = i -> put!(ch, i^2)
Threads.foreach(f, c)
end
julia> collect(d)
collect(d) = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400]تتطلب هذه الدالة جوليا 1.6 أو أحدث.
Base.Threads.@spawn — MacroThreads.@spawn [:default|:interactive] exprأنشئ Task وschedule لتشغيله على أي خيط متاح في مجموعة الخيوط المحددة (:default إذا لم يتم تحديدها). يتم تخصيص المهمة لخيط بمجرد أن يصبح متاحًا. للانتظار حتى تنتهي المهمة، قم باستدعاء wait على نتيجة هذه الماكرو، أو قم باستدعاء fetch للانتظار ثم الحصول على قيمة الإرجاع الخاصة بها.
يمكن إدخال القيم في @spawn عبر $، والذي ينسخ القيمة مباشرة إلى الإغلاق الأساسي المُنشأ. وهذا يسمح لك بإدراج قيمة متغير، مما يعزل الكود غير المتزامن عن التغييرات في قيمة المتغير في المهمة الحالية.
قد يتغير الخيط الذي تعمل عليه المهمة إذا قامت المهمة بالتخلي، لذلك يجب عدم اعتبار threadid() ثابتًا لمهمة. انظر Task Migration، والدليل الأوسع حول التعددية في الخيوط لمزيد من التحذيرات المهمة. انظر أيضًا الفصل حول مجموعات الخيوط.
هذه الماكرو متاحة اعتبارًا من Julia 1.3.
إدخال القيم عبر $ متاح اعتبارًا من Julia 1.4.
يمكن تحديد مجموعة خيوط اعتبارًا من Julia 1.9.
أمثلة
julia> t() = println("Hello from ", Threads.threadid());
julia> tasks = fetch.([Threads.@spawn t() for i in 1:4]);
Hello from 1
Hello from 1
Hello from 3
Hello from 4Base.Threads.threadid — FunctionThreads.threadid() -> Intاحصل على رقم تعريف الخيط الحالي للتنفيذ. الخيط الرئيسي له ID 1.
أمثلة
julia> Threads.threadid()
1
julia> Threads.@threads for i in 1:4
println(Threads.threadid())
end
4
2
5
4!!! ملاحظة قد يتغير الخيط الذي تعمل عليه مهمة ما إذا كانت المهمة تتخلى، وهو ما يعرف بـ هجرة المهام. لهذا السبب، في معظم الحالات، ليس من الآمن استخدام threadid() للقيام بفهرسة، على سبيل المثال، مصفوفة من الكائنات المؤقتة أو ذات الحالة.
Base.Threads.maxthreadid — FunctionThreads.maxthreadid() -> Intاحصل على حد أدنى لعدد الخيوط (عبر جميع مجموعات الخيوط) المتاحة لعملية جوليا، مع دلالات الاستحواذ الذري. ستكون النتيجة دائمًا أكبر من أو تساوي threadid() بالإضافة إلى threadid(task) لأي مهمة كنت قادرًا على ملاحظتها قبل استدعاء maxthreadid.
Base.Threads.nthreads — FunctionThreads.nthreads(:default | :interactive) -> Intاحصل على العدد الحالي من الخيوط داخل مجموعة الخيوط المحددة. الخيوط في :interactive لها أرقام تعريف 1:nthreads(:interactive)، والخيوط في :default لها أرقام تعريف في nthreads(:interactive) .+ (1:nthreads(:default)).
انظر أيضًا BLAS.get_num_threads و BLAS.set_num_threads في مكتبة LinearAlgebra القياسية، و nprocs() في مكتبة Distributed القياسية و Threads.maxthreadid().
Base.Threads.threadpool — FunctionThreads.threadpool(tid = threadid()) -> Symbolيعيد مجموعة خيوط الخيط المحدد؛ إما :default أو :interactive أو :foreign.
Base.Threads.nthreadpools — FunctionThreads.nthreadpools() -> Intيُرجع عدد مجموعات الخيوط المكونة حاليًا.
Base.Threads.threadpoolsize — FunctionThreads.threadpoolsize(pool::Symbol = :default) -> Intاحصل على عدد الخيوط المتاحة لمجموعة الخيوط الافتراضية (أو لمجموعة الخيوط المحددة).
انظر أيضًا: BLAS.get_num_threads و BLAS.set_num_threads في مكتبة LinearAlgebra القياسية، و nprocs() في مكتبة Distributed القياسية.
Base.Threads.ngcthreads — FunctionThreads.ngcthreads() -> Intيُرجع عدد خيوط جمع القمامة (GC) المكونة حاليًا. يشمل ذلك كل من خيوط العلامة وخيوط المسح المتزامن.
انظر أيضًا Multi-Threading.
Atomic operations
atomic — Keywordعمليات المؤشر غير الآمنة متوافقة مع تحميل وتخزين المؤشرات المعلنة باستخدام _Atomic و std::atomic في C11 و C++23 على التوالي. قد يتم إلقاء خطأ إذا لم يكن هناك دعم لتحميل نوع جوليا T بشكل ذري.
انظر أيضًا: unsafe_load، unsafe_modify!، unsafe_replace!، unsafe_store!، unsafe_swap!
Base.@atomic — Macro@atomic var
@atomic order exقم بتحديد var أو ex على أنه يتم تنفيذه بشكل ذري، إذا كانت ex تعبيرًا مدعومًا. إذا لم يتم تحديد order، فإنه يت default إلى :sequentially_consistent.
@atomic a.b.x = new
@atomic a.b.x += addend
@atomic :release a.b.x = new
@atomic :acquire_release a.b.x += addendقم بتنفيذ عملية التخزين المعبر عنها على اليمين بشكل ذري وأعد القيمة الجديدة.
مع =, تترجم هذه العملية إلى استدعاء setproperty!(a.b, :x, new). مع أي عامل أيضًا، تترجم هذه العملية إلى استدعاء modifyproperty!(a.b, :x, +, addend)[2].
@atomic a.b.x max arg2
@atomic a.b.x + arg2
@atomic max(a.b.x, arg2)
@atomic :acquire_release max(a.b.x, arg2)
@atomic :acquire_release a.b.x + arg2
@atomic :acquire_release a.b.x max arg2قم بتنفيذ العملية الثنائية المعبر عنها على اليمين بشكل ذري. قم بتخزين النتيجة في الحقل في الوسيط الأول وأعد القيم (old, new).
تترجم هذه العملية إلى استدعاء modifyproperty!(a.b, :x, func, arg2).
انظر Per-field atomics القسم في الدليل لمزيد من التفاصيل.
أمثلة
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic a.x # fetch field x of a, with sequential consistency
1
julia> @atomic :sequentially_consistent a.x = 2 # set field x of a, with sequential consistency
2
julia> @atomic a.x += 1 # increment field x of a, with sequential consistency
3
julia> @atomic a.x + 1 # increment field x of a, with sequential consistency
3 => 4
julia> @atomic a.x # fetch field x of a, with sequential consistency
4
julia> @atomic max(a.x, 10) # change field x of a to the max value, with sequential consistency
4 => 10
julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency
10 => 10هذه الوظيفة تتطلب على الأقل Julia 1.7.
Base.@atomicswap — Macro@atomicswap a.b.x = new
@atomicswap :sequentially_consistent a.b.x = newيخزن new في a.b.x ويعيد القيمة القديمة لـ a.b.x.
تترجم هذه العملية إلى استدعاء swapproperty!(a.b, :x, new).
انظر إلى قسم Per-field atomics في الدليل لمزيد من التفاصيل.
أمثلة
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicswap a.x = 2+2 # استبدال الحقل x من a بـ 4، مع اتساق تسلسلي
1
julia> @atomic a.x # جلب الحقل x من a، مع اتساق تسلسلي
4تتطلب هذه الوظيفة على الأقل جوليا 1.7.
Base.@atomicreplace — Macro@atomicreplace a.b.x متوقع => مرغوب
@atomicreplace :sequentially_consistent a.b.x متوقع => مرغوب
@atomicreplace :sequentially_consistent :monotonic a.b.x متوقع => مرغوبقم بإجراء الاستبدال الشرطي المعبر عنه بواسطة الزوج بشكل ذري، مع إرجاع القيم (قديم، نجاح::Bool). حيث تشير نجاح إلى ما إذا كان الاستبدال قد اكتمل.
تترجم هذه العملية إلى استدعاء replaceproperty!(a.b, :x, متوقع, مرغوب).
انظر إلى الذرات حسب الحقل في القسم الخاص باليد في الدليل لمزيد من التفاصيل.
أمثلة
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomicreplace a.x 1 => 2 # استبدل الحقل x من a بـ 2 إذا كان 1، مع اتساق تسلسلي
(قديم = 1، نجاح = true)
julia> @atomic a.x # احصل على الحقل x من a، مع اتساق تسلسلي
2
julia> @atomicreplace a.x 1 => 2 # استبدل الحقل x من a بـ 2 إذا كان 1، مع اتساق تسلسلي
(قديم = 2، نجاح = false)
julia> xchg = 2 => 0; # استبدل الحقل x من a بـ 0 إذا كان 2، مع اتساق تسلسلي
julia> @atomicreplace a.x xchg
(قديم = 2، نجاح = true)
julia> @atomic a.x # احصل على الحقل x من a، مع اتساق تسلسلي
0تتطلب هذه الوظيفة على الأقل جوليا 1.7.
Base.@atomiconce — Macro@atomiconce a.b.x = value
@atomiconce :sequentially_consistent a.b.x = value
@atomiconce :sequentially_consistent :monotonic a.b.x = valueقم بإجراء تعيين شرطي للقيمة بشكل ذري إذا كانت غير محددة سابقًا، مع إرجاع القيمة success::Bool. حيث تشير success إلى ما إذا كان التعيين قد اكتمل.
تترجم هذه العملية إلى استدعاء setpropertyonce!(a.b, :x, value).
انظر إلى قسم Per-field atomics في الدليل لمزيد من التفاصيل.
أمثلة
julia> mutable struct AtomicOnce
@atomic x
AtomicOnce() = new()
end
julia> a = AtomicOnce()
AtomicOnce(#undef)
julia> @atomiconce a.x = 1 # تعيين الحقل x من a إلى 1، إذا كان غير محدد، مع اتساق تسلسلي
true
julia> @atomic a.x # جلب الحقل x من a، مع اتساق تسلسلي
1
julia> @atomiconce a.x = 1 # تعيين الحقل x من a إلى 1، إذا كان غير محدد، مع اتساق تسلسلي
falseتتطلب هذه الوظيفة على الأقل Julia 1.11.
Core.AtomicMemory — TypeAtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU}حجم ثابت DenseVector{T}. الوصول إلى أي من عناصره يتم بشكل ذري (مع ترتيب :monotonic). يجب أن يتم تعيين أي من العناصر باستخدام ماكرو @atomic وتحديد الترتيب بشكل صريح.
كل عنصر هو ذري بشكل مستقل عند الوصول إليه، ولا يمكن تعيينه بشكل غير ذري. حاليًا، لم يتم إكمال ماكرو @atomic والواجهة ذات المستوى الأعلى، ولكن اللبنات الأساسية لتنفيذ مستقبلي هي الدوال الداخلية Core.memoryrefget, Core.memoryrefset!, Core.memoryref_isassigned, Core.memoryrefswap!, Core.memoryrefmodify!, و Core.memoryrefreplace!.
للتفاصيل، انظر Atomic Operations
هذا النوع يتطلب Julia 1.11 أو أحدث.
هناك أيضًا معلمات اختيارية لترتيب الذاكرة لمجموعة الدوال unsafe، التي تختار النسخ المتوافقة مع C/C++ من هذه العمليات الذرية، إذا تم تحديد تلك المعلمة إلى unsafe_load، unsafe_store!، unsafe_swap!، unsafe_replace!، و unsafe_modify!.
تعتبر واجهات برمجة التطبيقات التالية قديمة، على الرغم من أن الدعم لها من المحتمل أن يستمر لعدة إصدارات.
Base.Threads.Atomic — TypeThreads.Atomic{T}يمسك بإشارة إلى كائن من النوع T، مما يضمن أنه يتم الوصول إليه فقط بشكل ذري، أي بطريقة آمنة للخيوط.
يمكن استخدام أنواع "بسيطة" معينة فقط بشكل ذري، وهي الأنواع البدائية مثل البوليان، والعدد الصحيح، وأنواع النقطة العائمة. هذه هي Bool، Int8...Int128، UInt8...UInt128، وFloat16...Float64.
يمكن إنشاء كائنات ذرية جديدة من قيم غير ذرية؛ إذا لم يتم تحديد أي منها، يتم تهيئة الكائن الذري بالصفر.
يمكن الوصول إلى الكائنات الذرية باستخدام صيغة []:
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> x[] = 1
1
julia> x[]
1تستخدم العمليات الذرية بادئة atomic_، مثل atomic_add!، atomic_xchg!، إلخ.
Base.Threads.atomic_cas! — FunctionThreads.atomic_cas!(x::Atomic{T}, cmp::T, newval::T) where Tقارن وعيّن x بشكل ذري
يقارن بشكل ذري القيمة في x مع cmp. إذا كانت متساوية، يكتب newval إلى x. خلاف ذلك، يترك x دون تعديل. يُرجع القيمة القديمة في x. من خلال مقارنة القيمة المعادة مع cmp (عبر ===) يعرف المرء ما إذا كان x قد تم تعديله والآن يحمل القيمة الجديدة newval.
للحصول على مزيد من التفاصيل، انظر إلى تعليمات cmpxchg في LLVM.
يمكن استخدام هذه الدالة لتنفيذ دلالات المعاملات. قبل المعاملة، يتم تسجيل القيمة في x. بعد المعاملة، يتم تخزين القيمة الجديدة فقط إذا لم يتم تعديل x في هذه الأثناء.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_cas!(x, 4, 2);
julia> x
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_cas!(x, 3, 2);
julia> x
Base.Threads.Atomic{Int64}(2)Base.Threads.atomic_xchg! — FunctionThreads.atomic_xchg!(x::Atomic{T}, newval::T) where Tقم بتبادل القيمة في x بشكل ذري
يقوم بتبادل القيمة في x مع newval بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw xchg في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_xchg!(x, 2)
3
julia> x[]
2Base.Threads.atomic_add! — FunctionThreads.atomic_add!(x::Atomic{T}, val::T) where T <: ArithmeticTypesيضيف val إلى x بشكل ذري
ينفذ x[] += val بشكل ذري. يُرجع القيمة القديمة. غير معرف لـ Atomic{Bool}.
للحصول على مزيد من التفاصيل، انظر إلى تعليمات atomicrmw add في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_add!(x, 2)
3
julia> x[]
5Base.Threads.atomic_sub! — FunctionThreads.atomic_sub!(x::Atomic{T}, val::T) where T <: ArithmeticTypesيطرح val من x بشكل ذري
ينفذ x[] -= val بشكل ذري. يُرجع القيمة القديمة. غير معرف لـ Atomic{Bool}.
للحصول على مزيد من التفاصيل، انظر إلى تعليمات atomicrmw sub في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_sub!(x, 2)
3
julia> x[]
1Base.Threads.atomic_and! — FunctionThreads.atomic_and!(x::Atomic{T}, val::T) where Tيؤدي عملية "AND" بتنسيق ذري بين x و val
يؤدي x[] &= val بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw and في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_and!(x, 2)
3
julia> x[]
2Base.Threads.atomic_nand! — FunctionThreads.atomic_nand!(x::Atomic{T}, val::T) where Tيؤدي إلى عملية NAND (ليس-و) بتنسيق ذري بين x و val
يؤدي إلى x[] = ~(x[] & val) بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw nand في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(3)
Base.Threads.Atomic{Int64}(3)
julia> Threads.atomic_nand!(x, 2)
3
julia> x[]
-3Base.Threads.atomic_or! — FunctionThreads.atomic_or!(x::Atomic{T}, val::T) where Tيؤدي عملية OR بتنسيق ذري بين x و val
يؤدي x[] |= val بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw or في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_or!(x, 7)
5
julia> x[]
7Base.Threads.atomic_xor! — FunctionThreads.atomic_xor!(x::Atomic{T}, val::T) where Tيؤدي إلى عملية XOR (الاستبعاد) بتزامن بين x و val
يؤدي إلى x[] $= val بشكل متزامن. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، انظر إلى تعليمات atomicrmw xor في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_xor!(x, 7)
5
julia> x[]
2Base.Threads.atomic_max! — FunctionThreads.atomic_max!(x::Atomic{T}, val::T) where Tقم بتخزين الحد الأقصى لـ x و val في x بشكل ذري
يؤدي إلى x[] = max(x[], val) بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw max في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(5)
Base.Threads.Atomic{Int64}(5)
julia> Threads.atomic_max!(x, 7)
5
julia> x[]
7Base.Threads.atomic_min! — FunctionThreads.atomic_min!(x::Atomic{T}, val::T) where Tقم بتخزين الحد الأدنى من x و val في x بشكل ذري
يؤدي إلى x[] = min(x[], val) بشكل ذري. يُرجع القيمة القديمة.
للحصول على مزيد من التفاصيل، راجع تعليمات atomicrmw min في LLVM.
أمثلة
julia> x = Threads.Atomic{Int}(7)
Base.Threads.Atomic{Int64}(7)
julia> Threads.atomic_min!(x, 5)
7
julia> x[]
5Base.Threads.atomic_fence — FunctionThreads.atomic_fence()أدخل سياج ذاكرة بتناسق تسلسلي
يُدخل سياج ذاكرة مع دلالات ترتيب بتناسق تسلسلي. هناك خوارزميات حيث يكون هذا مطلوبًا، أي حيث يكون ترتيب الاستحواذ/الإفراج غير كافٍ.
من المحتمل أن تكون هذه عملية مكلفة جدًا. نظرًا لأن جميع العمليات الذرية الأخرى في جوليا تحتوي بالفعل على دلالات الاستحواذ/الإفراج، يجب ألا تكون السياجات الصريحة ضرورية في معظم الحالات.
للحصول على مزيد من التفاصيل، راجع تعليمات fence في LLVM.
ccall using a libuv threadpool (Experimental)
Base.@threadcall — Macro@threadcall((cfunc, clib), rettype, (argtypes...), argvals...)يتم استدعاء ماكرو @threadcall بنفس طريقة استدعاء ccall ولكن يقوم بالعمل في خيط مختلف. هذا مفيد عندما تريد استدعاء دالة C محجوزة دون التسبب في حظر الخيط الحالي لـ julia. يتم تحديد التزامن بواسطة حجم مجموعة خيوط libuv، والتي تكون افتراضيًا 4 خيوط ولكن يمكن زيادتها عن طريق تعيين متغير البيئة UV_THREADPOOL_SIZE وإعادة تشغيل عملية julia.
لاحظ أن الدالة المستدعاة يجب ألا تستدعي مرة أخرى داخل Julia.
Low-level synchronization primitives
تُستخدم هذه الكتل الأساسية لإنشاء كائنات التزامن العادية.
Base.Threads.SpinLock — TypeSpinLock()أنشئ قفل دوران غير قابل لإعادة الدخول، يستخدم اختبارًا واختبارًا وتعيينًا. سيؤدي الاستخدام التكراري إلى حدوث حالة توقف. يجب استخدام هذا النوع من الأقفال فقط حول الشيفرة التي تستغرق وقتًا قليلاً للتنفيذ ولا تعيق (مثل إجراء الإدخال/الإخراج). بشكل عام، يجب استخدام ReentrantLock بدلاً من ذلك.
يجب أن يتطابق كل lock مع unlock. إذا كانت !islocked(lck::SpinLock) صحيحة، فإن trylock(lck) ينجح ما لم تكن هناك مهام أخرى تحاول الاحتفاظ بالقفل "في نفس الوقت".
تكون أقفال الدوران التي تستخدم اختبارًا واختبارًا وتعيينًا الأسرع حتى حوالي 30 خيطًا متنافسًا. إذا كان لديك المزيد من التنافس من ذلك، يجب النظر في أساليب تزامن مختلفة.