【问题标题】:Event driven design in cc语言中的事件驱动设计
【发布时间】:2013-01-10 07:56:06
【问题描述】:

这是一个小小的理论问题。 想象一个装满传感器的设备。现在,如果传感器 x 检测到某些东西,应该会发生一些事情。同时,如果检测到其他东西,比如两个传感器检测到两个不同的东西,那么这个设备的行为肯定会有所不同。

我从 webdesign (so javascript) 了解到事件,例如(使用 jquery)$(".x").on("click", function(){}) 或从 angularjs $scope.watch("name_of_var", function())

是否有可能在不使用复杂库的情况下在 C 中复制这种行为?

谢谢。

【问题讨论】:

  • 管理您刚刚描述的硬件场景无论有没有库都会很复杂(无论库有多复杂)。使用 C,您更接近于裸机。
  • 如果你需要复杂的行为而不使用复杂的库,你需要自己编写复杂的代码:)
  • 答案在很大程度上取决于系统的硬件:如果传感器可以产生中断,您需要做的就是提供您的自定义中断服务例程:它们将充当事件处理程序。另一方面,如果您必须轮询传感器的变化,您将不得不自己处理很多复杂性。
  • 你想要异步处理。它要求传感器的驱动程序引发中断,并在它们发生时做出响应。你没有提到操作系统,但如果传感器连接是通过套接字你可以使用文件(UNIX 示例:) aio 或 select() 因为套接字是文件。
  • libevlibeventlibuv 等库可以用作事件循环驱动的框架,但除此之外还有很多工作要做.

标签: c events event-driven-design


【解决方案1】:

我能想到的一个系统是订阅者通知模型。 您可能有一些东西可以处理您的传感器(例如,一个线程对其进行轮询以查看是否发生了某些事情)。当它检测到某些东西时,任务应该提出一种机制,以便让外部世界知道:这是通知过程。
另一方面,只有对您的传感器感兴趣的人才会收到通知,因此,subscription 方法应该可以解决这个问题。

现在,困难的部分来了。当传感器处理程序通知世界时,它必须花费太多时间这样做,否则它可能会错过其他事件。因此,必须有一个专门用于通知过程的任务(或线程)。另一方面,订阅者希望在收到此类通知事件时更新他们的一些数据。这显然是一个异步过程,因此订阅者必须向通知线程提供一个回调
最后,您应该使用时间戳标记您的事件,这样接收者就会知道他们收到的事件是否已过时以及是否应该丢弃它。
最后的事情可能看起来像下面的代码:


数据结构

/*
 * Some data structures to begin with
 */
struct event;
struct notifier;
struct subscription;
struct notify_sched;


typedef int (*notify_cbck)(struct event *evt, void *private);
/*
 *@type : a value to show the type of event
 *@t : the timestamp of the event
 *@value : a pointer towards the event data
 */
struct event {
    int type;
    struct timeval t; // the timestamp
    void *value;
};

/*
 * @type : the type in which the subscriber is interested
 * @cb : the callback that should be run when an event occur
 * @cb_data : the data to provide to the callback
 * @next,prev : doubly-linked list
 */
struct subscription {
    int type;
    notify_cbck cb;
    void *cb_data;
    struct subscription *next, *prev;
};

/*
 * This structure gathers the subscriptions of a given type.
 * @type : the event type
 * @subs : the subscription list
 * @mutex : a mutex to protect the list while inserting/removing subscriptions
 * @next,prev : link to other typed subscriptions
 */

struct typed_subscription {
    int type;
    struct subscription *subs;
    mutex_t mutex;
    struct typed_subscription *next, *prev;
};

/*
 * @magic : the ID of the event producer
 * @t_subs : the typed_subscription list
 * @mutex : a mutex to protect data when (un)registering new types to the producer
 * @next, prev : doubly-linked list ...
 */
struct notifier {
    int magic;
    struct typed_subscription *t_subs;
    mutex_t mutex;
    struct notifier *next, *prev;
};

/*
 * @ntf : the notifiers list
 * @mutex : a mutex to protect the ntf list
 * @th : something to identify the task that hosts the scheduler
 */
struct notify_sched {
    struct notifier *ntf;
    mutex_t mutex;
    pthread_t th; // I assume it's a classic pthread in this example.
};

我现在没有时间完成我的答案,稍后我将对其进行编辑以提供完整的示例。但是从数据结构开始,你应该得到一些想法。无论如何,希望这有点帮助。

【讨论】:

    【解决方案2】:

    我假设您拥有一个嵌入式系统,可以在单独的线程中访问中断或主要事件循环,否则这是不可能的..

    这里有一个事件处理的基本模型:

    #define NOEVENT 0
    
    typedef void *(*EventHandler)(void *);
    
    void *doNothing(void *p){/*do nothing absolutely*/ return NULL; }
    typedef struct _event{
      EventHandler handler;
    }Event, *PEvent;
    
    Event AllEvents[1000];
    unsigned short counter = 0;
    void InitEvents()
    {
        LOCK(AllEvents);
        for(int i = 0; i < 1000; i++){ 
            AllEvents[i].handler = doNothing;
        }
        UNLOCK(AllEvents);
    }
    void AddEvent(int EventType, EventHandler ev_handler)
    {
        LOCK(AllEvents);
        AllEvents[EventType].handler = ev_handler;
        UNLOCK(AllEvents);
    }
    
    void RemoveEvent(int EventType, EventHandler ev_handler)
    {
       LOCK(AllEvents);
       AllEvents[EventType] = doNothing;
       UNLOCK(AllEvents); /*to safeguard the event loop*/
    }
    
    /*to be run in separate thread*/
    void EventLoop()
    {
       int event = NOEVENT;
       EventHandler handler;
       while(1){
           while(event == NOEVENT)event=GetEvents();
           handler = AllEvents[event].handler;
           handler();/*perform on an event*/
      }
    }
    

    对不起,如果这有点幼稚..但这是我目前能想到的最好的。

    【讨论】:

      【解决方案3】:

      对于事件,您需要事件循环,它检测实际事件(例如,来自网络的数据)发生,然后生成软件事件结构并调用适当的事件处理程序,或者在更复杂的系统中,事件处理程序链,直到处理程序将事件标记为已接受。如果没有处理程序,或者没有注册的处理程序接受事件,则忽略事件。

      通常事件循环在一个库中,除了库本身可能产生的任何事件之外,它还具有 API 供应用程序处理事件处理程序并发送特定于应用程序的事件。基于事件的应用程序的一个问题是,使用两个都希望拥有自己的事件循环的库通常很复杂,除非库开发人员特别注意允许使用除库自己之外的其他事件循环。

      除非它是一个非常低级的实时系统,否则事件循环不做忙等待是至关重要的。在 Linux/Unix/Posix 代码中,事件循环通常围绕 select() 或 poll() 系统函数工作。当没有事件时,事件循环调用此函数,超时匹配下一个定时器事件的时间(如果有定时器事件)。除了超时,如果任何指定的文件句柄(通常是网络或 IPC 套接字)准备好进行读/写/错误,以及是否存在未以其他方式处理的中断,select()/poll() 也会返回.然后事件循环代码检查函数返回的原因,生成并分发必要的事件,当一切都完成后,再次调用 select()/poll() 函数。

      在基于事件的系统中同样重要的是,事件处理程序不能阻塞,因为它是由事件循环调用的函数,所以事件循环不会在后台某处继续,处理程序函数调用是事件循环的一部分。因此处理函数必须只处理可用数据,最好是快速处理,然后存储必要的状态以便稍后继续,并返回以等待下一个事件。对于必须阻塞的操作,必须启动另一个线程。同样对于长计算,计算必须被切成小块以允许事件循环也运行,或者计算必须在另一个线程中发生。 GUI 应用程序标题栏中烦人的“无响应”通常意味着:应用程序程序员懒惰/无能并阻塞了事件循环,因此无法对操作系统事件做出反应。

      所以,是的,使用 C 构建基于事件的系统非常容易。只需在其中有一个带有 select()/poll() 的循环,定义事件类型,为事件创建数据结构,并拥有一个函数列表对于每个事件类型,以新事件结构作为参数调用的指针。

      【讨论】:

        【解决方案4】:

        因为我找到了这条消息,但我并没有完全找到我的解决方案,所以我将基于 https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/ 的代码发布在此处以供其他人使用。

        事件.h

        typedef struct s_Arguments {
            char *name;
            int value;
        } Arguments; // Treat this as you please
         
        typedef struct s_Event {
            char *name;
            Arguments args;
        } Event;
         
        typedef struct {
            char *name;
            void (*handler)(void*);
        } Handler;
        
        struct s_EventContainer {
            int *exitFlag;
            Event *poll;
            Handler *listeners;
            void (*registerEvent)(char *name, void*);
            void (*emit)(char *name, Arguments args);
            int listenersc;
            int pollc;
        };
        
        struct s_EventContainer eventContainer;
        
        void _registerEvent(char *name, void*);
        void _emitEvent(char *name, Arguments args);
        
        void popPoll();
        
        void find_and_exec_handler_in_listeners(char *name, Arguments *args);
        
        void * fn_eventsDigest(void * p_Data);
        
        void *testFired(Arguments *args);
        

        事件.c

        #include "event.h"
        
        
        int BLOCK_POP, BLOCK_EMIT;
        
        
        void _registerEvent(char *name, void *cb) {
            LOG("_registerEvent '%s'", name);
            if (!eventContainer.listeners) eventContainer.listeners = malloc(sizeof(Handler));    
            Handler listener = {name, cb};  
            eventContainer.listeners[eventContainer.listenersc] = listener;  
            eventContainer.listenersc++;  
            eventContainer.listeners = realloc(eventContainer.listeners, sizeof(Handler) * (eventContainer.listenersc+1));  
        }
        
        void _emitEvent(char *name, Arguments args) {
            LOG("Emit event '%s' with args value %d", name, args.value);
            while(BLOCK_EMIT) asm("nop");
            BLOCK_POP = 1;
            if (!eventContainer.poll) eventContainer.poll = malloc(sizeof(Event));    
            Event poll = {name, args};  
            eventContainer.poll[eventContainer.pollc] = poll;  
            eventContainer.pollc++;  
            eventContainer.poll = realloc(eventContainer.poll, sizeof(Event) * (eventContainer.pollc+1));  
            BLOCK_POP = 0;
        }
        
        void popPoll(){
            int* temp = malloc((eventContainer.pollc - 1) * sizeof(Event));
            memcpy(temp, eventContainer.poll+1, (eventContainer.pollc - 1) * sizeof(Event));
            eventContainer.pollc -= 1;
            eventContainer.poll = realloc(eventContainer.poll, eventContainer.pollc * sizeof(Event) + sizeof(Event));
            memcpy(eventContainer.poll, temp, eventContainer.pollc * sizeof(Event));
            temp = NULL;
        }
        
        
        
        void find_and_exec_handler_in_listeners(char *name, Arguments *args){
            int i;
            for (i=0; i < eventContainer.listenersc; i++) {
                if (strcmp(eventContainer.listeners[i].name, name ) == 0 ) {
                    (*eventContainer.listeners[i].handler)(args);
                }
            }
        }
        
        
        void * fn_eventsDigest(void * p_Data) {
            struct s_EventContainer *eventContainer = (struct s_EventContainer *)p_Data;
        
            BLOCK_POP = 0;
            BLOCK_EMIT = 0;
            
            while(*(eventContainer->exitFlag) == 0) {
                if ( eventContainer->pollc > 0 && BLOCK_POP == 0) {
                    BLOCK_EMIT = 1;
                    Event ev = eventContainer->poll[0];
                    find_and_exec_handler_in_listeners(ev.name, &ev.args);
                    popPoll(); 
                    BLOCK_EMIT = 0;
                }
            usleep(1*1000);
            }
            printf("Event Loop::exiting...\r\n"); fflush(stdout);
        }
        
        void *testFired(Arguments *args) {
            LOG("test event fired with value %d \r\n", args->value);
        }
        

        main.c

        #include "event.h"
        #include <pthread.h>
        
        struct s_EventContainer eventContainer = {
            &Data.exitFlag, //exit loop
            NULL, //poll
            NULL, //listeners
            _registerEvent,
            _emitEvent,
            0,
            0
        };
        
        pthread_t events_thread;
        
        int main(int argc, char** argv) {
        
            eventContainer.registerEvent("test", &testFired);
            
            int ret = pthread_create (&events_thread, NULL, fn_eventsDigest, &eventContainer);
            if (ret) {
                fprintf (stderr, "%s", strerror (ret));
            }
            pthread_setname_np(events_thread,"events_thread");
           
            //....
            sleep(2);
            Arguments args;
            args.name = "test";
            args.value = 10;
            eventContainer.emit("test", args);
        
            pthread_join (events_thread, NULL);
            return (EXIT_SUCCESS);
        }
        

        我愿意接受建议。

        【讨论】:

        • 最大的问题是内存泄漏。将 temp 设置为 NULL 不会释放该内存。我也会通过让队列只能增长来避免做这么多的重新分配;除了拥有事件计数之外,还有一个 maximum_so_far 并在需要时使用它来重新分配内存。你也不需要做整个临时的事情,当你把队列改组时,你可以使用 memcpy。或者设置最大队列长度并使用队列循环样式,这完全避免了重新分配和 memcpys。最后,我不会使用字符串标识符,主要是为了避免使用 strcmp。
        猜你喜欢
        • 1970-01-01
        • 2012-02-10
        • 2013-03-24
        • 2010-12-17
        • 2015-05-08
        • 1970-01-01
        • 1970-01-01
        • 2010-10-24
        • 1970-01-01
        相关资源
        最近更新 更多