Multi-Threading
Base.Threads.@threads
— MacroThreads.@threads [schedule] for ... end
ماكرو لتنفيذ حلقة for
بالتوازي. يتم توزيع مساحة التكرار على مهام ذات حبيبات خشنة. يمكن تحديد هذه السياسة بواسطة وسيط schedule
. ينتظر تنفيذ الحلقة تقييم جميع التكرارات.
انظر أيضًا: @spawn
و pmap
في Distributed
.
مساعدة موسعة
الدلالات
ما لم يتم تحديد ضمانات أقوى بواسطة خيار الجدولة، فإن الحلقة التي يتم تنفيذها بواسطة ماكرو @threads
لها الدلالات التالية.
تنفذ ماكرو @threads
جسم الحلقة بترتيب غير محدد وبتزامن محتمل. لا تحدد التعيينات الدقيقة للمهام وخيوط العمل. يمكن أن تكون التعيينات مختلفة في كل تنفيذ. يجب ألا تفترض شفرة جسم الحلقة (بما في ذلك أي شفرة يتم استدعاؤها بشكل غير مباشر منها) أي افتراضات حول توزيع التكرارات على المهام أو خيط العمل الذي يتم تنفيذها فيه. يجب أن يكون جسم الحلقة لكل تكرار قادرًا على إحراز تقدم مستقل عن التكرارات الأخرى وأن يكون خاليًا من سباقات البيانات. على هذا النحو، قد تؤدي التزامنات غير الصالحة عبر التكرارات إلى توقف بينما قد تؤدي الوصولات غير المتزامنة إلى سلوك غير محدد.
على سبيل المثال، تشير الشروط أعلاه إلى أن:
- يجب أن يتم تحرير القفل المأخوذ في تكرار داخل نفس التكرار.
- التواصل بين التكرارات باستخدام بدائل حظر مثل
Channel
s غير صحيح. - الكتابة فقط إلى المواقع غير المشتركة عبر التكرارات (ما لم يتم استخدام قفل أو عملية ذرية).
- ما لم يتم استخدام الجدول الزمني
:static
، قد تتغير قيمةthreadid()
حتى داخل تكرار واحد. انظرTask Migration
.
المجدولون
بدون وسيط المجدول، فإن الجدولة الدقيقة غير محددة وتختلف عبر إصدارات جوليا. حاليًا، يتم استخدام :dynamic
عندما لا يتم تحديد المجدول.
وسيط schedule
متاح اعتبارًا من جوليا 1.5.
:dynamic
(افتراضي)
ينفذ المجدول :dynamic
التكرارات ديناميكيًا إلى خيوط العمل المتاحة. تفترض التنفيذ الحالي أن عبء العمل لكل تكرار متساوي. ومع ذلك، قد تتم إزالة هذا الافتراض في المستقبل.
تعتبر هذه الخيار الجدولي مجرد تلميح لآلية التنفيذ الأساسية. ومع ذلك، يمكن توقع بعض الخصائص. عدد Task
s المستخدمة بواسطة المجدول :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 4
Base.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[]
2
Base.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[]
5
Base.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[]
1
Base.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[]
2
Base.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[]
-3
Base.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[]
7
Base.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[]
2
Base.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[]
7
Base.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[]
5
Base.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 خيطًا متنافسًا. إذا كان لديك المزيد من التنافس من ذلك، يجب النظر في أساليب تزامن مختلفة.