Linunx的sleep,usleep,select,nonasleep對比與應用

2021-02-20 鳶尾楚楚

前言

時鐘換算:

1秒(s) = 1000 毫秒(ms) = 1,000,000 微秒(μs) = 1,000,000,000 納秒(ns) = 1,000,000,000,000 皮秒(ps)

程序掛起主要有以下幾種:

sleep, usleep, select, pselect, nanosleep;

它們的精度不同,在不同的應用場景下需要不同的函數;

 

一、用法1.1 函數名: sleep

頭文件:

#include <unistd.h>  // 在gcc編譯器中,使用的頭文件因gcc版本的不同而不同

功  能: 執行掛起指定的秒數

語  法: unsigned sleep(unsigned seconds);

 

示例:

#include<stdio.h>

int main()

{

  int a;

  a=1;

  printf("hello");

  sleep(a);      

  printf("world"); 

  return 0;

}

 

1.2 函數名: usleep

功  能: usleep功能把進程掛起一段時間, 單位是微秒(百萬分之一秒);

頭文件: #include <unistd.h>

語  法:

void usleep(int micro_seconds);

返回值: 無

內容說明:本函數可暫時使程序停止執行。參數 micro_seconds 為要暫停的微秒數(us)。

注 意:

用在Linux的測試環境下面。

參 見:usleep() 與sleep()類似,用於延遲掛起進程。進程被掛起放到reday queue。

是一般情況下,延遲時間數量級是秒的時候,儘可能使用sleep()函數。

如果延遲時間為幾十毫秒(1ms = 1000us),或者更小,儘可能使用usleep()函數。這樣才能最佳的利用CPU時間.

 

1.3 函數名:select

功能:I/O多工機制

用來等待文件描述符狀態的改變。

頭文件:

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

語法:

int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval*timeout);

參數說明:

maxfdp:最大的文件描述符加1,如果我有三個文件節點1、4、6,那第一個參數就為7(6+1)

readfds:可讀文件節點集,類型為fd_set。

通過FD_ZERO(&readfd);初始化節點集;

然後通過FD_SET(fd, &readfd);把需要監聽是否可讀的節點加入節點集

readfds:可寫文件節點集中,類型為fd_set。操作方法和第二個參數一樣。

errorfds:檢查節點錯誤集。

timeout:超時參數,類型為struct timeval,用於設置超時時間,

分別可設置秒timeout.tv_sec和微秒timeout.tv_usec。

返回值:

執行成功則返回文件描述符狀態已經改變的個數,

如果返回0,則表示在描述符在狀態改變前已經超過了timeout時間。

當有錯誤發生時,返回-1, 且錯誤原因保存在errno中。

 

定時器用法:

//test

select tv.tv_sec = 0;

tv.tv_usec = 500000;

ret = select(0, NULL, NULL, NULL, &tv);

if (-1 == ret){

    fprintf(stderr, "select error. errno = %d [%s]\n", errno, strerror(errno));

}

 

1.4 函數名:pselect

功能:I/O多工機制

頭文件:

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

#include <sys/select.h>

語法:

int pselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timespec* timeout,

const sigset_t *sigmask);

說明:

pselect()和select()作用幾乎一樣,都是用來等待文件描述符狀態的改變,

不過pselect() 還可以判斷是否有信號(signal)發生。

 

定時器用法:

req.tv_sec = nDelay/1000000;

req.tv_nsec = (nDelay%1000000) * 1000;

ret = pselect(0, NULL, NULL, NULL, &req, NULL);

if (-1 == ret){

fprintf(stderr, "select error. errno = %d [%s]\n", errno, strerror(errno));

}

 

1.5 函數名:nonasleep

功能:

nanosleep()函數會導致當前的線程將暫停執行,直到rqtp參數所指定的時間間隔。

或者在指定時間間隔內有信號傳遞到當前線程,將引起當前線程調用信號捕獲函數或終止該線程。

 

頭文件:

#include<time.h>

#include<sys/time.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/select.h>

 

語法:

  int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);

 

描述:

nanosleep()函數會導致當前的線程將暫停執行,直到rqtp參數所指定的時間間隔。

或者在指定時間間隔內有信號傳遞到當前線程,將引起當前線程調用信號捕獲函數或終止該線程。

  暫停時間可能超過請求時間,因為參數值是sleep粒度的整數倍數或者因為其他活動的系統調度。

  但是,除了被信號中斷的情況下,暫停時間不會少於rqtp指定的時間,由系統時鐘CLOCK_REALTIME測量。

  使用nanosleep()函數對其他行為沒有影響,不堵塞任何信號。

 

返回值:

0 :請示的時間間隔結束。

-1:信號中斷或失敗,並設置errno。

如果rmtp參數不為空,它所引用的timespec結構更新為包含剩餘時間的間隔量(請求的時間減去實際睡眠時間)。

如果rmtp參數為NULL,不返回的剩餘時間。

 

示例:

req.tv_sec = 0;

req.tv_nsec = 1000;

ret = nanosleep(&req, NULL);

if (-1 == ret) {

fprintf (stderr, "\t nanousleep %8u not support\n", nDelay);

}

 

二、精確度對比

如果用於一些 C/S 數據交互等 100ms級以上精度的程序掛起,用sleep, usleep都可以。

如果是在多線程應用場景中,如用於音視頻數據的同步,需要100ms以下的精度的程序掛起,則只能用select, 和 nanosleep.

 

這是因為,雖然sleep()和nanosleep()都是使進程睡眠一段時間後被喚醒,但是二者的實現完全不同。

Linux中並沒有提供系統調用sleep(),

sleep()是在庫函數中實現的,它是通過調用alarm()來設定報警時間,

調用sigsuspend()將進程掛起在信號SIGALARM上。

 

nanosleep()則是Linux中的系統調用,它是使用定時器來實現的,

該調用使調用進程睡眠,並往定時器隊列上加入一個timer_list型定時器,time_list結構裡包括喚醒時間以及喚醒後執行的函數,

通過nanosleep()加入的定時器的執行函數僅僅完成喚醒當前進程的功能。

系統通過一定的機制定時檢查這些隊列(比如通過系統調用陷入核心後,從核心返回用戶態前,

要檢查當前進程的時間片是否已經耗盡,如果是則調用schedule()函數重新調度,該函數中就會檢查定時器隊列,另外慢中斷返回前也會做此檢查),

如果定時時間已超過,則執行定時器指定的函數喚醒調用進程。

 

 

三、工程應用

這是FFmpeg的程序掛起,

int av_usleep(unsigned usec)

{

#if HAVE_NANOSLEEP

struct timespec ts = { usec / 1000000, usec % 1000000 * 1000 };

while (nanosleep(&ts, &ts) < 0 && errno == EINTR);

return 0;

#elif HAVE_USLEEP

return usleep(usec);

#elif HAVE_SLEEP

Sleep(usec / 1000);

return 0;

#else

return AVERROR(ENOSYS);

#endif

}


相關焦點