* ecore_thread_run: Add a facility to run heavy code in another thread
authorcedric <cedric@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Fri, 31 Jul 2009 17:06:11 +0000 (17:06 +0000)
committercedric <cedric@7cbeb6ba-43b4-40fd-8cce-4c39aea84d33>
Fri, 31 Jul 2009 17:06:11 +0000 (17:06 +0000)
that still integrate cleanly with the EFL.

ecore_thread_run need two callbacks :

* func_heavy is called from another thread and should not use the
EFL except Eina, but carefully.

* func_end is called when func_heavy is done, but from inside ecore
main loop, so you can at this point call every EFL functions without
fear.

Note :

The system automatically detect how many CPU you have and will spread
the load on all of them.

You must not assume that the result will come in the same order you
requested it. Depend on each CPU load and how heavy the function on it
are.

git-svn-id: http://svn.enlightenment.org/svn/e/trunk/ecore@41555 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33

configure.ac
src/lib/ecore/Ecore.h
src/lib/ecore/Makefile.am
src/lib/ecore/ecore.c
src/lib/ecore/ecore_private.h
src/lib/ecore/ecore_thread.c [new file with mode: 0644]

index 989212c..1a1bb4e 100644 (file)
@@ -197,6 +197,12 @@ requirements_ecore_x=""
 requirements_ecore_win32=""
 requirements_ecore_wince=""
 
+# basic pthread support
+AC_CHECK_HEADER(pthread.h,
+  [
+     AC_DEFINE(BUILD_PTHREAD, 1, [Build thread worker])
+  ]
+)
 
 ### Additional options to configure
 
index f6c8a5f..db95288 100644 (file)
@@ -59,6 +59,8 @@
 # include <signal.h>
 #endif
 
+#include <Eina.h>
+
 #ifndef TRUE
 # define TRUE 1
 #endif
@@ -300,6 +302,8 @@ extern "C" {
    EAPI void         ecore_pipe_write_close(Ecore_Pipe *p);
    EAPI void         ecore_pipe_read_close(Ecore_Pipe *p);
 
+   EAPI Eina_Bool ecore_thread_run(void (*func_heavy)(void *data), void (*func_end)(void *data), const void *data);
+
    EAPI double ecore_time_get(void);
    EAPI double ecore_loop_time_get(void);
 
index e7e4219..5c2dcf6 100644 (file)
@@ -34,7 +34,8 @@ ecore_strbuf.c \
 ecore_time.c \
 ecore_timer.c \
 ecore_tree.c \
-ecore_value.c
+ecore_value.c \
+ecore_thread.c
 
 libecore_la_LIBADD = @dlopen_libs@ @EINA_LIBS@ @EVIL_LIBS@ @WIN32_LIBS@ -lm
 libecore_la_LDFLAGS = -no-undefined @lt_enable_auto_import@ -version-info @version_info@ @ecore_release_info@
index 2b5c476..59dcafc 100644 (file)
@@ -89,6 +89,7 @@ ecore_init(void)
        if (_ecore_fps_debug) _ecore_fps_debug_init();
        _ecore_signal_init();
        _ecore_exe_init();
+       ecore_thread_init();
        _ecore_loop_time = ecore_time_get();
      }
 
@@ -114,6 +115,7 @@ ecore_shutdown(void)
    if (_ecore_fps_debug) _ecore_fps_debug_shutdown();
    _ecore_poller_shutdown();
    _ecore_animator_shutdown();
+   ecore_thread_shutdown();
    _ecore_exe_shutdown();
    _ecore_idle_enterer_shutdown();
    _ecore_idle_exiter_shutdown();
index 5a7eee1..1badc67 100644 (file)
@@ -444,7 +444,8 @@ void          _ecore_fps_debug_init(void);
 void          _ecore_fps_debug_shutdown(void);
 void          _ecore_fps_debug_runtime_add(double t);
 
-
+int ecore_thread_init(void);
+int ecore_thread_shutdown(void);
 
 extern int    _ecore_fps_debug;
 extern double _ecore_loop_time;
diff --git a/src/lib/ecore/ecore_thread.c b/src/lib/ecore/ecore_thread.c
new file mode 100644 (file)
index 0000000..4512ab0
--- /dev/null
@@ -0,0 +1,221 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_EVIL
+# include <Evil.h>
+#endif
+
+#include <pthread.h>
+
+#include "ecore_private.h"
+#include "Ecore.h"
+
+typedef struct _Ecore_Pthread_Worker Ecore_Pthread_Worker;
+typedef struct _Ecore_Pthread Ecore_Pthread;
+
+struct _Ecore_Pthread_Worker
+{
+   void (*func_heavy)(void *data);
+   void (*func_end)(void *data);
+
+   const void *data;
+};
+
+static int _ecore_thread_count_max = 0;
+static int _ecore_thread_count = 0;
+
+static int _ecore_thread_init = 0;
+static Eina_List *_ecore_thread = NULL;
+static int ECORE_THREAD_PIPE_DEL = 0;
+static Ecore_Event_Handler *del_handler = NULL;
+
+static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#ifdef BUILD_PTHREAD
+static void
+_ecore_thread_pipe_free(void *data __UNUSED__, void *event)
+{
+   Ecore_Pipe *p = event;
+
+   ecore_pipe_del(p);
+}
+
+static int
+_ecore_thread_pipe_del(void *data __UNUSED__, int type __UNUSED__, void *event __UNUSED__)
+{
+   /* This is a hack to delay pipe destruction until we are out of it's internal loop. */
+   return 0;
+}
+
+static void
+_ecore_thread_end(pthread_t *thread)
+{
+   Ecore_Pipe *p;
+
+   if (pthread_join(*thread, (void**) &p) != 0)
+     return ;
+
+   ecore_event_add(ECORE_THREAD_PIPE_DEL, p, _ecore_thread_pipe_free, NULL);
+}
+
+static void *
+_ecore_thread_worker(Ecore_Pipe *p)
+{
+   Ecore_Pthread_Worker *work;
+   pthread_t *pth;
+
+   pthread_mutex_lock(&_mutex);
+   _ecore_thread_count++;
+   pthread_mutex_unlock(&_mutex);
+
+ on_error:
+
+   while (_ecore_thread)
+     {
+       pthread_mutex_lock(&_mutex);
+
+       if (!_ecore_thread)
+         {
+            pthread_mutex_unlock(&_mutex);
+            break;
+         }
+
+       work = eina_list_data_get(_ecore_thread);
+       _ecore_thread = eina_list_remove_list(_ecore_thread, _ecore_thread);
+
+       pthread_mutex_unlock(&_mutex);
+
+       work->func_heavy((void*) work->data);
+
+       ecore_pipe_write(p, &work, sizeof (Ecore_Pthread_Worker*));
+     }
+
+   pthread_mutex_lock(&_mutex);
+   if (_ecore_thread)
+     {
+       pthread_mutex_unlock(&_mutex);
+       goto on_error;
+     }
+   _ecore_thread_count--;
+
+   pthread_mutex_unlock(&_mutex);
+
+   work = malloc(sizeof (Ecore_Pthread_Worker) + sizeof (pthread_t));
+   if (!work) return NULL;
+
+   work->data = (void*) (work + 1);
+   work->func_heavy = NULL;
+   work->func_end = (void*) _ecore_thread_end;
+
+   pth = (void*) work->data;
+   *pth = pthread_self();
+
+   ecore_pipe_write(p, &work, sizeof (Ecore_Pthread_Worker*));
+
+   return p;
+}
+
+static void
+_ecore_thread_handler(__UNUSED__ void *data, void *buffer, unsigned int nbyte)
+{
+   Ecore_Pthread_Worker *work;
+
+   if (nbyte != sizeof (Ecore_Pthread_Worker*)) return ;
+
+   work = *(Ecore_Pthread_Worker**)buffer;
+
+   work->func_end((void*) work->data);
+
+   free(work);
+}
+#endif
+
+int
+ecore_thread_init(void)
+{
+   _ecore_thread_init++;
+
+   if (_ecore_thread_init > 1) return _ecore_thread_init;
+
+   _ecore_thread_count_max = eina_cpu_count();
+   if (_ecore_thread_count_max < 0)
+     _ecore_thread_count_max = 1;
+
+   ECORE_THREAD_PIPE_DEL = ecore_event_type_new();
+   del_handler = ecore_event_handler_add(ECORE_THREAD_PIPE_DEL, _ecore_thread_pipe_del, NULL);
+
+   return _ecore_thread_init;
+}
+
+int
+ecore_thread_shutdown(void)
+{
+   _ecore_thread_init--;
+
+   if (!_ecore_thread_init)
+     {
+       /* FIXME: If function are still running in the background, should we kill them ? */
+       ecore_event_handler_del(del_handler);
+       del_handler = NULL;
+     }
+
+   return _ecore_thread_init;
+}
+
+/*
+ * ecore_thread_run provide a facility for easily managing heavy task in a
+ * parallel thread. You should provide two function, the first one, func_heavy,
+ * that will do the heavy work in another thread (so you should not use the
+ * EFL in it except Eina if you are carefull), and the second one, func_end,
+ * that will be called in Ecore main loop when func_heavy is done. So you
+ * can use all the EFL inside this function.
+ *
+ * Be aware, that you can't make assumption on the result order of func_end
+ * after many call to ecore_thread_run, as we start as much thread as the
+ * host CPU can handle.
+ */
+EAPI Eina_Bool
+ecore_thread_run(void (*func_heavy)(void *data),
+                 void (*func_end)(void *data),
+                 const void *data)
+{
+#ifdef BUILD_PTHREAD
+   Ecore_Pthread_Worker *work;
+   Ecore_Pipe *p;
+   pthread_t thread;
+
+   work = malloc(sizeof (Ecore_Pthread_Worker));
+   if (!work) return EINA_FALSE;
+
+   work->func_heavy = func_heavy;
+   work->func_end = func_end;
+   work->data = data;
+
+   pthread_mutex_lock(&_mutex);
+   _ecore_thread = eina_list_append(_ecore_thread, work);
+
+   if (_ecore_thread_count == _ecore_thread_count_max)
+     return EINA_TRUE;
+
+   pthread_mutex_unlock(&_mutex);
+
+   /* One more thread could be created. */
+   p = ecore_pipe_add(_ecore_thread_handler, NULL);
+
+   if (pthread_create(&thread, NULL, (void*) _ecore_thread_worker, p) == 0)
+     return EINA_TRUE;
+
+   return EINA_FALSE;
+#else
+   /*
+     If no thread and as we don't want to break app that rely on this
+     facility, we will lock the interface until we are done.
+    */
+   func_heavy(data);
+   func_end(data);
+
+   return EINA_TRUE;
+#endif
+}
+