C++中自定义sleep、条件变量sleep实例

sleep的作用无需多说,几乎每种语言都提供了类似的函数,调用起来也很简单。sleep的作用无非是让程序等待若干时间,而为了达到这样的目的,其实有很多种方式,最简单的往往也是最粗暴的,我们就以下面这段代码来举例说明(注:本文提及的程序编译运行环境为Linux)

代码如下:

/* filename: test.cpp */ 
#include <stdio.h> 
#include <unistd.h> 
#include <pthread.h> 
#include <signal.h> 
 
class TestServer 

public: 
    TestServer() : run_(true) {}; 
    ~TestServer(){}; 
 
    void Start() 
    { 
        pthread_create(&thread_, NULL, ThreadProc, (void*)this); 
    } 
    void Stop() 
    { 
        run_ = false; 
    } 
    void Wait() 
    { 
        pthread_join(thread_, NULL); 
    } 
    void Proc() 
    { 
        int count = 0; 
        while (run_) 
        { 
            printf("sleep count:%d\n", ++count); 
            sleep(5); 
        } 
    } 
 
private: 
    bool run_; 
    pthread_t thread_; 
 
    static void* ThreadProc(void* arg) 
    { 
        TestServer* me = static_cast<TestServer*>(arg); 
        me->Proc(); 
        return NULL; 
    } 
}; 
 
TestServer g_server; 
 
void StopService() 

    g_server.Stop(); 

 
void StartService() 

    g_server.Start(); 
    g_server.Wait(); 

 
void SignalHandler(int sig) 

    switch(sig) 
    { 
        case SIGINT: 
            StopService(); 
        default: 
            break; 
    } 

 
int main(int argc, char* argv[]) 

    signal(SIGINT, SignalHandler); 
    StartService(); 
    return 0; 
}

这段代码描述了一个简单的服务程序,为了简化我们省略了服务的处理逻辑,也就是Proc函数的内容,这里我们只是周期性的打印某条语句,为了达到周期性的目的,我们用sleep来实现,每隔5秒钟打印一次。在main函数中我们对SIGINT信号进行了捕捉,当程序在终端启动之后,如果你输入ctr+c,这会向程序发送中断信号,一般来说程序会退出,而这里我们捕捉到了这个信号,会按我们自己的逻辑来处理,也就是调用server的Stop函数。执行编译命令

代码如下:

$ g++ test.cpp -o test -lpthread

然后在终端输入./test运行程序,这时程序每隔5秒会在屏幕上打印一条语句,按下ctl+c,你会发现程序并没有立即退出,而是等待了一会儿才退出,究其原因,当按下ctl+c发出中断信号时,程序捕捉到并执行自己的逻辑,也就是调用了server的Stop函数,运行标记位run_被置为false,Proc函数检测到run_为false则退出循环,程序结束,但有可能(应该说大多数情况都是如此)此时Proc正好执行到sleep那一步,而sleep是将程序挂起,由于我们捕捉到了中断信号,因此它不会退出,而是继续挂起直到时间满足为止。这个sleep显然显得不够优雅,下面介绍两种能快速退出的方式。

自定义sleep

在我们调用系统提供的sleep时我们是无法在函数内部做其它事情的,基于此我们就萌生出一种想法,如果在sleep中能够检测到退出变量,那岂不是就能快速退出了,没错,事情就是这样子的,通过自定义sleep,我们将时间片分割成更小的片段,每隔一个片段检测一次,这样就能将程序的退出延迟时间缩小为这个更小的片段,自定义的sleep如下

代码如下:

void sleep(int seconds, const bool* run) 

    int count = seconds * 10; 
    while (*run && count > 0) 
    { 
        --count; 
        usleep(100000); 
    } 
}

需要注意的是,这个sleep的第二个参数必须是指针类型的,因为我们需要检测到它的实时值,而不只是使用它传入进来的值,相应的函数调用也得稍作修改,完整的代码如下

代码如下:

/* filename: test2.cpp */ 
#include <stdio.h> 
#include <unistd.h> 
#include <pthread.h> 
#include <signal.h> 
 
class TestServer 

public: 
    TestServer() : run_(true) {}; 
    ~TestServer(){}; 
 
    void Start() 
    { 
     pthread_create(&thread_, NULL, ThreadProc, (void*)this); 
    } 
 
    void Stop() 
    { 
       run_ = false; 
    } 
 
    void Wait() 
    { 
        pthread_join(thread_, NULL); 
    } 
 
    void Proc() 
    { 
        int count = 0; 
        while (run_) 
        { 
            printf("sleep count:%d\n", ++count); 
            sleep(5, &run_); 
        } 
    } 
 
private: 
    bool run_; 
    pthread_t thread_; 
 
    void sleep(int seconds, const bool* run) 
    { 
        int count = seconds * 10; 
        while (*run && count > 0) 
        { 
            --count; 
            usleep(100000); 
 
        } 
    } 
 
    static void* ThreadProc(void* arg) 
    { 
        TestServer* me = static_cast<TestServer*>(arg); 
        me->Proc(); 
        return NULL; 
    } 
}; 
 
TestServer g_server; 
 
void StopService() 

   g_server.Stop(); 

 
void StartService() 

    g_server.Start(); 
   g_server.Wait(); 

 
void SignalHandler(int sig) 

    switch(sig) 
    { 
        case SIGINT: 
            StopService(); 
        default: 
            break; 
    } 

 
int main(int argc, char* argv[]) 

    signal(SIGINT, SignalHandler); 
    StartService(); 
    return 0; 
}

编译g++ test2.cpp -o test,运行./test,当程序启动之后按ctl+c,看程序是不是很快就退出了。

其实这种退出并不是立马退出,而是将sleep的等待时间分成了更小的时间片,上例是0.1秒,也就是说在按下ctr+c之后,程序其实还会延时0到0.1秒才会退出,只不过这个时间很短,看上去就像立马退出一样。

用条件变量实现sleep

大致的思想就是,在循环时等待一个条件变量,并设置超时时间,如果在这个时间之内有其它线程触发了条件变量,等待会立即退出,否则会一直等到设置的时间,这样就可以通过对条件变量的控制来实现sleep,并且可以在需要的时候立马退出。

条件变量往往会和互斥锁搭配使用,互斥锁的逻辑很简单,如果一个线程获取了互斥锁,其它线程就无法获取,也就是说如果两个线程同时执行到了pthread_mutex_lock语句,只有一个线程会执行完成,而另一个线程会阻塞,直到有线程调用pthread_mutex_unlock才会继续往下执行。所以我们往往在多线程访问同一内存区域时会用到互斥锁,以防止多个线程同时修改某一块内存区域。本例用到的函数有如下几个,互斥锁相关函数有

代码如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 
int pthread_mutex_lock(pthread_mutex_t *mutex); 
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

以上函数功能分别是初始化、加锁、解锁、销毁。条件变量相关函数有

代码如下:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 
int pthread_cond_signal(pthread_cond_t *cond); 
int pthread_cond_destroy(pthread_cond_t *cond);

以上函数功能分别是初始化、超时等待条件变量、触发条件变量、销毁。这里需要解释一下pthread_cond_timedwait和pthread_cond_signal函数

代码如下:

pthread_cond_timedwait

这个函数调用之后会阻塞,也就是类似sleep的作用,但是它会在两种情况下被唤醒:1、条件变量cond被触发时;2、系统时间到达abstime时,注意这里是绝对时间,不是相对时间。它比sleep的高明之处就在第一点。另外它还有一个参数是mutex,当执行这个函数时,它的效果等同于在函数入口处先对mutex加锁,在出口处再对mutex解锁,当有多线程调用这个函数时,可以按这种方式去理解

pthread_cond_signal
它只有一个参数cond,作用很简单,就是触发等待cond的线程,注意,它一次只会触发一个,如果要触发所有等待cond的县城,需要用到pthread_cond_broadcast函数,参数和用法都是一样的

有了以上背景知识,就可以更加优雅的实现sleep,主要关注Proc函数和Stop函数,完整的代码如下

代码如下:

/* filename: test3.cpp */ 
#include <stdio.h> 
#include <unistd.h> 
#include <pthread.h> 
#include <signal.h> 
#include <sys/time.h> 
 
class TestServer 

public: 
    TestServer() : run_(true)  
    { 
        pthread_mutex_init(&mutex_, NULL); 
        pthread_cond_init(&cond_, NULL); 
    }; 
    ~TestServer() 
    { 
        pthread_mutex_destroy(&mutex_); 
        pthread_cond_destroy(&cond_); 
    }; 
 
    void Start() 
    { 
        pthread_create(&thread_, NULL, ThreadProc, (void*)this); 
    } 
 
    void Stop() 
    { 
        run_ = false; 
        pthread_mutex_lock(&mutex_); 
        pthread_cond_signal(&cond_); 
        pthread_mutex_unlock(&mutex_); 
   } 
 
    void Wait() 
    { 
        pthread_join(thread_, NULL); 
    } 
 
    void Proc() 
    { 
        pthread_mutex_lock(&mutex_); 
        struct timeval now; 
        int count = 0; 
        while (run_) 
        { 
            printf("sleep count:%d\n", ++count); 
            gettimeofday(&now, NULL); 
            struct timespec outtime; 
            outtime.tv_sec = now.tv_sec + 5; 
            outtime.tv_nsec = now.tv_usec * 1000; 
            pthread_cond_timedwait(&cond_, &mutex_, &outtime); 
        } 
        pthread_mutex_unlock(&mutex_); 
    } 
 
private: 
    bool run_; 
    pthread_t thread_; 
    pthread_mutex_t mutex_; 
    pthread_cond_t cond_; 
 
    static void* ThreadProc(void* arg) 
    { 
        TestServer* me = static_cast<TestServer*>(arg); 
        me->Proc(); 
        return NULL; 
    } 
}; 
 
TestServer g_server; 
 
void StopService() 

    g_server.Stop(); 

 
void StartService() 

    g_server.Start(); 
    g_server.Wait(); 

 
void SignalHandler(int sig) 

    switch(sig) 
    { 
        case SIGINT: 
            StopService(); 
        default: 
            break; 
    } 

 
int main(int argc, char* argv[]) 

    signal(SIGINT, SignalHandler); 
    StartService(); 
    return 0; 
}

和test2.cpp一样,编译之后运行,程序每隔5秒在屏幕打印一行输出,输入ctr+c,程序会立马退出

(0)

相关推荐

  • 详解C++编程中类的成员变量和成员函数的相关知识

    C++类的成员变量和成员函数 类是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型.类这种数据类型是一个包含成员变量和成员函数的一个集合. 类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存空间.但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型,本身不占用内存空间,而变量的值则需要内存来存储. 类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定:而普通函数是独立的,作

  • C++中的extern声明变量详解

    extern声明变量无外乎如下两种: 1.声明全局变量 2.声明函数 今天我们只谈extern,什么const.static之类等等与之相关或不相关的一律忽略,下面就分别对以上两种情况一一讲解 声明和定义 既然提到extern声明变量,那我们就必须搞清楚声明和定义的区别. 这里我们将普通数据变量和函数统称变量.从内存分配角度来说,声明和定义的区别在于声明一个变量不会分配内存,而定义一个变量会分配内存.一个变量可以被声明多次,但是只能被定义一次. 基于以上前提,我们可以把声明和定义类比为指针和内存

  • C++中结构体的类型定义和初始化以及变量引用

    C++结构体类型的定义和初始化 有时需要将不同类型的数据组合成一个有机的整体,以供用户方便地使用.这些组合在一个整体中的数据是互相联系的.例如,一个学生的学号.姓名.性别.年龄.成绩.家庭地址等项,都是这个学生的属性,见图 可以看到学号(num).姓名(name).性别(sex).年龄(age).成绩(score ).地址(addr)是与姓名为"Li Fun"的学生有关的.如果在程序中将num,name,sex,age,score,addr分别定义为互相独立的变量,就难以反映出它们之间

  • c++静态局部变量和静态函数示例

    在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存.但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效.但有的时候我们需要在两次调用之间对变量的值进行保存.通常的想法是定义一个全局变量来实现.但是这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便.静态局部变量正好可以解决这个问题.静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值.该变量在全局数据区内分配内存:静态局部变量在程序执行到该对象的声明

  • C++中变量的类型与作用域学习教程

    C++ 变量类型 变量其实只不过是程序可操作的存储区的名称.C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以由字母.数字和下划线字符组成.它必须以字母或下划线开头.大写字母和小写字母是不同的,因为 C++ 是大小写敏感的. 基于前一章讲解的基本类型,有以下几种基本的变量类型,将在下面进行讲解: 类型 描述 bool 存储值 true 或 false. char 通常是一个八位字节(一个字节).这是一个整数类型

  • 深入理解C++中变量的存储类别和属性

    C++变量的存储类别(动态存储.静态存储.自动变量.寄存器变量.外部变量) 动态存储方式与静态存储方式 我们已经了解了变量的作用域.作用域是从空间的角度来分析的,分为全局变量和局部变量. 变量还有另一种属性--存储期(storage duration,也称生命期).存储期是指变量在内存中的存在期间.这是从变量值存在的时间角度来分析的.存储期可以分为静态存储期(static storage duration)和动态存储期(dynamic storage duration).这是由变量的静态存储方式

  • C++基础入门教程(二):数据、变量、宏等

    哎,木了个头的,这书太详细了~看得累人 前面部分太过基础了,很多语法方法的东西我也不重复记录了~ 今天才看看C++涉及到数据的一些基础点吧,我把书中稍微比较有营养的部分抽离出来 1.初始化 一般情况下,我们声明和初始化变量是这样的:int iNum = 10; 其实这是延续了C语言的语法,C++还有一种语法,是这样的:int iNum(10); 其实大部分人还是习惯用第一种吧(我也是),但如果哪天我们在别人的代码里看到第二种形式,不要被吓蒙了~ 2.宏定义 宏,用最粗俗,不,是最通俗的说法来解释

  • 详解C++中的指针结构体数组以及指向结构体变量的指针

    C++结构体数组 一个结构体变量中可以存放一组数据(如一个学生的学号.姓名.成绩等数据).如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组.结构体数组与以前介绍过的数值型数组的不同之处在于:每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项. 定义结构体数组和定义结构体变量的方法相仿,定义结构体数组时只需声明其为数组即可.如: struct Student //声明结构体类型Student { int num; char name[20]; char sex; i

  • C++空类及没有成员变量的类的大小实例分析

    众所周知的C++中空类的大小为1,但是除了空类之外的其他一些没有成员变量的类的大小,还是有相当一部分开发人员对此有很多不明之处的. 这里我们以如下代码为例: #include using namespace std; class a {}; class b{}; class c :public a{ virtual void fun() = 0; }; class d :public b, public c{}; int main() { cout << "sizeof(a)&quo

  • C++不使用变量求字符串长度strlen函数的实现方法

    本文实例讲述了C++不使用变量求字符串长度strlen函数的实现方法.分享给大家供大家参考.具体实现方法如下: 1.strlen的源码实现: size_t strlen(const char *str) //strlen不做内存非法判断,如果是NULL,会core. { const char *eos=str; while(*eos++); return (eos-str-1); } 2.常见面试题会要求不使用额外变量,实现strlen函数: 实现一: int strlen(const char

  • C++ const修饰变量和修饰函数介绍

    const修饰变量 关于const最常见的一个面试题是这样的:char *const和const char*有什么区别,大家都知道const修饰符代表的是常量,即const修饰的变量一旦被初始化是不能被更改的,这两个类型一个代表的是指针不可变,一个代表指针指向内容不可变,但具体哪个对应哪个,很多人一直搞不清楚. 有这样一个规律,const修饰的是它前面所有的数据类型,如果const在最前面,那么把它和它后面第一个数据类行交换.比如上面的const char*交换之后就是char const *,

随机推荐