كيفية جعل مؤشر الترابط ينام أقل من مللي ثانية على نظام التشغيل Windows

StackOverflow https://stackoverflow.com/questions/85122

  •  01-07-2019
  •  | 
  •  

سؤال

على نظام التشغيل Windows أواجه مشكلة لم أواجهها مطلقًا على نظام Unix.هذه هي الطريقة لجعل الخيط ينام لمدة تقل عن ميلي ثانية واحدة.في نظام Unix، يكون لديك عادةً عدد من الخيارات (النوم، والاستخدام، والنوم النانوي) التي تناسب احتياجاتك.ومع ذلك، على نظام التشغيل Windows، لا يوجد سوى ينام مع التفاصيل ميلي ثانية واحدة.

في نظام Unix، يمكنني استخدام استخدام select استدعاء النظام لإنشاء نوم بالميكروثانية وهو أمر بسيط جدًا:

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

كيف يمكنني تحقيق نفس الشيء على نظام التشغيل Windows؟

هل كانت مفيدة؟

المحلول 18

على نظام التشغيل Windows استخدام select يفرض عليك تضمين وينسوك المكتبة التي يجب تهيئتها بهذه الطريقة في تطبيقك:

WORD wVersionRequested = MAKEWORD(1,0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);

ومن ثم لن يسمح لك التحديد بالاتصال بك بدون أي مقبس، لذا يتعين عليك القيام بالمزيد لإنشاء طريقة microsleep:

int usleep(long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, &dummy, &tv);
}

كل طرق الاستخدام التي تم إنشاؤها تُرجع صفرًا عندما تكون ناجحة وغير صفرية للأخطاء.

نصائح أخرى

يشير هذا إلى سوء فهم وظائف النوم.المعلمة التي تمررها هي أ الحد الأدنى وقت النوم.ليس هناك ما يضمن أن مؤشر الترابط سوف يستيقظ بعد الوقت المحدد بالضبط.في الواقع، لا "تنشط" سلاسل الرسائل على الإطلاق، بل يتم اختيارها للتنفيذ بواسطة المجدول.قد يختار المجدول الانتظار لفترة أطول بكثير من مدة السكون المطلوبة لتنشيط مؤشر ترابط، خاصة إذا كان مؤشر ترابط آخر لا يزال نشطًا في تلك اللحظة.

كما يقول جويل، لا يمكنك "النوم" بشكل هادف (أي.التخلي عن وحدة المعالجة المركزية المجدولة) لمثل هذه الفترات القصيرة.إذا كنت تريد التأجيل لفترة قصيرة، فأنت بحاجة إلى الدوران، والتحقق بشكل متكرر من مؤقت عالي الدقة مناسب (على سبيل المثال."مؤقت الأداء") ونأمل ألا يستبقك شيء ذو أولوية عالية على أي حال.

إذا كنت تهتم حقًا بالتأخيرات الدقيقة لمثل هذه الأوقات القصيرة، فيجب ألا تستخدم Windows.

استخدم الموقتات عالية الدقة المتوفرة في winmm.lib.يرى هذا على سبيل المثال.

نعم، أنت بحاجة إلى فهم الكميات الزمنية لنظام التشغيل لديك.على نظام التشغيل Windows، لن تحصل على أوقات دقة تبلغ 1 مللي ثانية إلا إذا قمت بتغيير كمية الوقت إلى 1 مللي ثانية.(باستخدام timeBeginPeriod()/timeEndPeriod() على سبيل المثال) لا يزال هذا لا يضمن أي شيء حقًا.حتى الحمل القليل أو برنامج تشغيل جهاز واحد سيئ سوف يؤدي إلى التخلص من كل شيء.

يساعد SetThreadPriority()، ولكنه خطير للغاية.لا يزال بإمكان برامج تشغيل الأجهزة السيئة أن تدمرك.

أنت بحاجة إلى بيئة حوسبة فائقة التحكم حتى تعمل هذه الأشياء القبيحة على الإطلاق.

#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

نعم يستخدم بعض وظائف kernel غير الموثقة، ولكنه يعمل بشكل جيد للغاية، وأنا استخدم SleepShort(0.5);في بعض خواطري

إذا كنت تريد الكثير من التفصيل فأنت في المكان الخطأ (في مساحة المستخدم).

تذكر أنه إذا كنت في مساحة المستخدم، فإن وقتك لن يكون دقيقًا دائمًا.

يمكن للمجدول بدء سلسلة المحادثات (أو التطبيق) وجدولتها، بحيث تعتمد على جدولة نظام التشغيل.

إذا كنت تبحث عن شيء محدد عليك أن تذهب:1) في مساحة kernel (مثل السائقين) 2) اختر RTOs.

على أي حال ، إذا كنت تبحث عن بعض التفاصيل (ولكن تذكر المشكلة مع مساحة المستخدم) ، تتطلع إلى وظيفة QueryPerformancounter ودالة QueryPerformanceFrequency في MSDN.

كما أشار العديد من الأشخاص، يعتمد النوم والوظائف الأخرى ذات الصلة بشكل افتراضي على "علامة النظام".هذا هو الحد الأدنى للوحدة الزمنية بين مهام نظام التشغيل؛المجدول، على سبيل المثال، لن يعمل بشكل أسرع من هذا.حتى مع نظام التشغيل في الوقت الفعلي، لا تقل علامة النظام عادةً عن 1 مللي ثانية.على الرغم من أنه قابل للضبط، إلا أن هذا له آثار على النظام بأكمله، وليس فقط وظيفة النوم لديك، لأن برنامج الجدولة الخاص بك سيتم تشغيله بشكل متكرر أكثر، ومن المحتمل أن يؤدي إلى زيادة الحمل على نظام التشغيل الخاص بك (مقدار الوقت الذي يستغرقه برنامج الجدولة لتشغيله، مقابل وقت تشغيله).مقدار الوقت الذي يمكن تنفيذ المهمة فيه).

الحل لذلك هو استخدام جهاز ساعة خارجي عالي السرعة.ستسمح لك معظم أنظمة Unix بتحديد مؤقتاتك وساعة مختلفة لاستخدامها، بدلاً من ساعة النظام الافتراضية.

ماذا تنتظر والذي يتطلب مثل هذه الدقة؟بشكل عام إذا كنت يحتاج لتحديد هذا المستوى من الدقة (على سبيل المثال.بسبب الاعتماد على بعض الأجهزة الخارجية) فأنت تستخدم نظامًا أساسيًا خاطئًا ويجب أن تنظر إلى نظام التشغيل في الوقت الفعلي.

بخلاف ذلك، يجب أن تفكر في ما إذا كان هناك حدث يمكنك مزامنته، أو في الحالة الأسوأ، فقط مشغول بانتظار وحدة المعالجة المركزية واستخدام واجهة برمجة تطبيقات عداد الأداء العالي لقياس الوقت المنقضي.

بشكل عام، سيستمر السكون على الأقل حتى حدوث مقاطعة النظام التالية.ومع ذلك ، فإن هذا يعتمد على إعدادات موارد مؤقت الوسائط المتعددة.قد يتم ضبطه على شيء ما يقرب من 1 مللي ثانية ، حتى أن بعض الأجهزة تسمح بالتشغيل عند فترات المقاطعة البالغة 0.9765625 (القرار الفعلي المقدمة من NtQueryTimerResolution سوف تظهر 0.9766 ولكن هذا خطأ في الواقع.لا يمكنهم وضع الرقم الصحيح في القرار الفعلي شكل.إنها 0.9765625 مللي ثانية عند 1024 مقاطعة في الثانية).

هناك استثناء واحد يسمح لنا بالهروب من حقيقة أنه قد يكون من المستحيل النوم لمدة أقل من فترة الانقطاع:إنه المشهور Sleep(0).هذه أداة قوية للغاية ولا يتم استخدامها كما ينبغي!إنه يتخلى عن التذكير بالشريحة الزمنية للخيط.بهذه الطريقة سيتوقف الخيط حتى يجبر المجدول الخيط على الحصول على خدمة وحدة المعالجة المركزية مرة أخرى. Sleep(0) هي خدمة غير متزامنة، وسيجبر الاستدعاء المجدول على الاستجابة بشكل مستقل عن المقاطعة.

والطريقة الثانية هي استخدام waitable object.وظيفة الانتظار مثل WaitForSingleObject() يمكن أن تنتظر حدثا ما.من أجل جعل الخيط في وضع السكون في أي وقت، وأيضًا في نظام الميكروثانية، يحتاج الخيط إلى إعداد بعض سلاسل الخدمة التي ستولد حدثًا بالتأخير المطلوب.سيقوم خيط "النوم" بإعداد هذا الخيط ثم يتوقف مؤقتًا عند وظيفة الانتظار حتى يقوم خيط الخدمة بتعيين الحدث الذي يشير إليه.

بهذه الطريقة يمكن لأي خيط أن "ينام" أو ينتظر في أي وقت.يمكن أن يكون مؤشر ترابط الخدمة معقدًا للغاية وقد يقدم خدمات على مستوى النظام مثل الأحداث الموقوتة بدقة ميكروثانية.ومع ذلك، فإن دقة الميكروثانية قد تجبر مؤشر ترابط الخدمة على الدوران على خدمة زمنية عالية الدقة لمدة مقاطعة واحدة على الأكثر (~1 مللي ثانية).إذا تم توخي الحذر ، يمكن أن يعمل هذا بشكل جيد للغاية ، حيث أنظمة متعددة المعالجات أو متعددة النواة.لا يضر الدوران بمقدار مللي ثانية بشكل كبير في النظام متعدد النواة، عندما يتم التعامل بعناية مع قناع التقارب لخيط الاتصال وخيط الخدمة.

يمكن زيارة الكود والوصف والاختبار على مشروع الطابع الزمني لويندوز

لدي نفس المشكلة ولا يبدو أن هناك أي شيء أسرع من مللي ثانية، حتى Sleep(0).مشكلتي هي الاتصال بين العميل وتطبيق الخادم حيث أستخدم وظيفة _InterlockedExchange للاختبار والضبط قليلاً ثم أنام (0).

أحتاج حقًا إلى إجراء آلاف العمليات في الثانية بهذه الطريقة، وهي لا تعمل بالسرعة التي خططت لها.

نظرًا لأن لدي عميل رفيع يتعامل مع المستخدم، والذي بدوره يستدعي وكيلًا ثم يتحدث بعد ذلك إلى سلسلة رسائل، فسوف أتحرك قريبًا لدمج سلسلة الرسائل مع الوكيل بحيث لا تكون هناك حاجة إلى واجهة حدث.

فقط لإعطائكم فكرة عن مدى بطء هذا النوم، أجريت اختبارًا لمدة 10 ثوانٍ لإجراء حلقة فارغة (حصلت على ما يقرب من 18.000.000 حلقة) بينما مع وجود الحدث، حصلت على 180.000 حلقة فقط.أي أبطأ 100 مرة!

في الواقع، سيؤدي استخدام وظيفة usleep هذه إلى حدوث تسرب كبير للذاكرة/الموارد.(اعتمادا على عدد المرات التي يتم الاتصال بها)

استخدم هذه النسخة المصححة (عذرًا، لا يمكنك التعديل؟)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}

كما ذكر الجميع، ليس هناك بالفعل أي ضمانات بشأن وقت النوم.لكن لا أحد يريد أن يعترف أنه في بعض الأحيان، في نظام خامل، يمكن أن يكون أمر usleep دقيقًا للغاية.خاصة مع نواة لا دغدغة.يتوفر بها نظام التشغيل Windows Vista، كما يتوفر بها نظام التشغيل Linux منذ الإصدار 2.6.16.

توجد حبات Tickless للمساعدة في تحسين عمر بطارية أجهزة الكمبيوتر المحمولة:راجع.فائدة powertop إنتل.

في هذه الحالة، صادف أنني قمت بقياس أمر Linux usleep الذي يحترم وقت النوم المطلوب بدقة شديدة، وصولاً إلى ستة ميكرو ثانية.

لذلك، ربما يريد OP شيئًا يعمل تقريبًا في معظم الأوقات على نظام خامل، ويكون قادرًا على طلب جدولة ثانية صغيرة!أنا في الواقع أريد ذلك على نظام التشغيل Windows أيضًا.

يبدو أيضًا Sleep(0) وكأنه Boost::thread::yield()، أي المصطلحات أكثر وضوحًا.

أتساءل عما إذا كان يعزز- تتميز الأقفال الموقوتة بدقة أفضل.لأنه بعد ذلك يمكنك فقط قفل كائن المزامنة (mutex) الذي لا يقوم أحد بإصداره على الإطلاق، وعندما يتم الوصول إلى المهلة، تابع ...يتم تعيين المهلات باستخدام Boost::system_time + Boost::millithans & cie (تم إهمال xtime).

جرب استخدام SetWaitableTimer...

جرب Boost::xtime وtimed_wait()

لديه دقة النانو ثانية.

فقط استخدم النوم (0).من الواضح أن 0 أقل من مللي ثانية.الآن، يبدو ذلك مضحكًا، لكنني جاد.يخبر Sleep(0) Windows أنه ليس لديك أي شيء لتفعله الآن، ولكنك تريد إعادة النظر فيه بمجرد تشغيل المجدول مرة أخرى.وبما أنه من الواضح أنه لا يمكن جدولة سلسلة المحادثات للتشغيل قبل تشغيل المجدول نفسه، فهذا هو أقصر تأخير ممكن.

لاحظ أنه يمكنك تمرير رقم ميكروثانية إلى نومك، لكن الأمر نفسه ينطبق أيضًا على void usleep(__int64 t) { Sleep(t/1000);} - لا توجد ضمانات للنوم فعليًا في تلك الفترة.

وظيفة النوم التي تكون أقل من ميلي ثانية واحدة

لقد وجدت أن النوم (0) يعمل بالنسبة لي.في نظام به حمل يقارب 0% على وحدة المعالجة المركزية في إدارة المهام، قمت بكتابة برنامج وحدة تحكم بسيط ودخلت وظيفة السكون (0) في وضع السكون لمدة تتراوح من 1 إلى 3 ميكروثانية، وهو أقل بكثير من مللي ثانية.

ولكن من الإجابات المذكورة أعلاه في هذا الموضوع، أعلم أن مقدار النوم (0) الذي ينام يمكن أن يختلف بشكل أكبر بكثير من هذا على الأنظمة ذات التحميل الكبير لوحدة المعالجة المركزية.

ولكن كما أفهمها، لا ينبغي استخدام وظيفة النوم كمؤقت.يجب استخدامه لجعل البرنامج يستخدم أقل نسبة ممكنة من وحدة المعالجة المركزية وينفذه بشكل متكرر قدر الإمكان.بالنسبة لأغراضي، مثل تحريك مقذوف عبر الشاشة في لعبة فيديو بشكل أسرع بكثير من بكسل واحد في المللي ثانية، أعتقد أن النوم (0) يعمل.

كل ما عليك فعله هو التأكد من أن فترة النوم أصغر بكثير من أكبر قدر من الوقت الذي تنام فيه.لا تستخدم وضع السكون كمؤقت ولكن فقط لجعل اللعبة تستخدم أقل قدر ممكن من نسبة وحدة المعالجة المركزية.يمكنك استخدام وظيفة منفصلة لا علاقة لها بالنوم للتعرف على مرور فترة معينة من الوقت ثم تحريك المقذوف بمقدار بكسل واحد عبر الشاشة - في وقت يبلغ 1/10 من المللي ثانية أو 100 ميكروثانية على سبيل المثال .

الكود الزائف سيذهب إلى شيء من هذا القبيل.

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

أعلم أن الإجابة قد لا تصلح للمشكلات أو البرامج المتقدمة ولكنها قد تصلح لبعض البرامج أو أكثر.

إذا كان هدفك هو "انتظر لفترة قصيرة جدًا من الوقت" لأنك تفعل spinwait, ، فهناك مستويات متزايدة من الانتظار يمكنك القيام بها.

void SpinOnce(ref Int32 spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;

   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

لذلك إذا كان هدفك هو الانتظار بالفعل فقط قليلا, ، يمكنك استخدام شيء مثل:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(ref spin);
}

والفضيلة هنا هي أننا ننتظر لفترة أطول في كل مرة، وفي النهاية ننام تمامًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top