Working with threads is hard.
Ecore helps to do so a bit easier, but as the example in ecore_thread_example.c shows, there's a lot to consider even when doing the most simple things.
We'll be going through this thorough example now, showing how the differents aspects of Ecore_Thread are used, but users are encourage to avoid threads unless it's really the only option, as they always add more complexity than the program usually requires.
Ecore Threads come in two flavors, short jobs and feedback jobs. Short jobs just run the given function and are more commonly used for small tasks where the main loop does not need to know how the work is going in between. The short job in our example is so short we had to artificially enlarge it with sleep()
. Other than that, it also uses threads local data to keep the data we are working with persistent across different jobs ran by the same system thread. This data will be freed when the no more jobs are pending and the thread is terminated. If the data doesn't exist in the thread's storage, we create it and save it there for future jobs to find it. If creation fails, we cancel ourselves, so the main loop knows that we didn't just exit normally, meaning the job could not be done. The main part of the function checks in each iteration if it was canceled by the main loop, and if it was, it stops processing and clears the data from the storage (we assume cancel
means no one else will need this, but this is really application dependent).
static void
_local_data_free(void *data)
{
Thread_Data *td = data;
char *str;
{
printf("Freeing string: %s\n", str);
free(str);
}
free(td);
}
static void
{
Thread_Data *td;
int i;
if (!td)
{
td = calloc(1, sizeof(Thread_Data));
if (!td)
{
return;
}
}
for (i = 0; i < 10; i++)
{
char buf[200];
{
break;
}
snprintf(buf, sizeof(buf), "Thread %p: String number %d", th, i);
sleep(1);
struct _Ecore_Thread Ecore_Thread
A handle for threaded jobs.
Definition: Ecore_Common.h:1729
Eina_Bool ecore_thread_local_data_add(Ecore_Thread *thread, const char *key, void *value, Eina_Free_Cb cb, Eina_Bool direct)
Adds some data to a hash local to the thread.
Definition: ecore_thread.c:1254
Eina_Bool ecore_thread_check(Ecore_Thread *thread)
Checks if a thread is pending cancellation.
Definition: ecore_thread.c:892
void * ecore_thread_local_data_find(Ecore_Thread *thread, const char *key)
Gets data stored in the hash local to the given thread.
Definition: ecore_thread.c:1333
Eina_Bool ecore_thread_local_data_del(Ecore_Thread *thread, const char *key)
Deletes from the thread's hash the data corresponding to the given key.
Definition: ecore_thread.c:1354
Eina_Bool ecore_thread_cancel(Ecore_Thread *thread)
Cancels a running thread.
Definition: ecore_thread.c:741
EINA_API Eina_List * eina_list_append(Eina_List *list, const void *data)
Appends the given data to the given linked list.
Definition: eina_list.c:584
#define EINA_LIST_FREE(list, data)
Definition for the macro to remove each list node while having access to each node's data.
Definition: eina_list.h:1629
#define EINA_FALSE
boolean value FALSE (numerical value 0)
Definition: eina_types.h:533
#define EINA_UNUSED
Used to indicate that a function parameter is purposely unused.
Definition: eina_types.h:339
}
}
Feedback jobs, on the other hand, run tasks that will inform back to the main loop its progress, send partial data as is processed, just ping saying it's still alive and processing, or anything that needs the thread to talk back to the main loop.
static void
{
time_t t;
int i, count;
Feedback_Thread_Data *ftd = NULL;
App_Msg *msg;
char *name;
for (i = 0; i < count; i++)
{
char buf[32];
snprintf(buf, sizeof(buf), "data%d", i);
if (!ftd)
continue;
break;
else
ftd = NULL;
}
if (!ftd)
return;
if (!it)
goto the_end;
void * ecore_thread_global_data_find(const char *key)
Gets data stored in the hash shared by all threads.
Definition: ecore_thread.c:1451
EINA_API Eina_Iterator * eina_file_ls(const char *dir)
Gets an iterator to list the content of a directory.
Definition: eina_file_posix.c:631
static Eina_Lock_Result eina_lock_take_try(Eina_Lock *mutex)
Attempts to take a lock if possible.
structure of an iterator
Definition: eina_iterator.h:159
msg = calloc(1, sizeof(App_Msg));
t = time(NULL);
{
if (time(NULL) >= (t + 2))
{
break;
}
#define EINA_ITERATOR_FOREACH(itr, data)
Definition for the macro to iterate over all elements easily.
Definition: eina_iterator.h:448
EINA_API void eina_stringshare_del(Eina_Stringshare *str)
Notes that the given string has lost an instance.
Definition: eina_stringshare.c:533
Finally, one more feedback job, but this one will be running outside of Ecore's pool, so we can use the pool for real work and keep this very light function unchecked. All it does is check if some condition is met and send a message to the main loop telling it it's time to close.
static void
{
App_Data *ad = data;
App_Msg *msg;
while (1)
{
int msgs;
msgs = ad->msgs_received;
if (msgs == ad->max_msgs)
{
msg = calloc(1, sizeof(App_Msg));
msg->all_done = 1;
return;
}
Eina_Bool ecore_thread_feedback(Ecore_Thread *thread, const void *msg_data)
Sends data from the worker thread to the main loop.
Definition: ecore_thread.c:1037
static Eina_Bool eina_condition_wait(Eina_Condition *cond)
Causes a thread to wait until signaled by the condition.
static Eina_Lock_Result eina_lock_release(Eina_Lock *mutex)
Releases a lock.
}
}
Every now and then the program prints its status, counting threads running and pending jobs.
static void
_print_status(void)
{
int active, pending_total, pending_feedback, pending_short, available;
printf("Status:\n\t* Active threads: %d\n"
"\t* Available threads: %d\n"
"\t* Pending short jobs: %d\n"
"\t* Pending feedback jobs: %d\n"
"\t* Pending total: %d\n", active, available, pending_short,
pending_feedback, pending_total);
}
int ecore_thread_pending_get(void)
Gets the number of short jobs waiting for a thread to run.
Definition: ecore_thread.c:1169
int ecore_thread_available_get(void)
Gets the number of threads available for running tasks.
Definition: ecore_thread.c:1230
int ecore_thread_pending_feedback_get(void)
Gets the number of feedback jobs waiting for a thread to run.
Definition: ecore_thread.c:1181
int ecore_thread_active_get(void)
Gets the number of active threads running jobs.
Definition: ecore_thread.c:1162
int ecore_thread_pending_total_get(void)
Gets the total number of pending jobs.
Definition: ecore_thread.c:1193
In our main loop, we'll be receiving messages from our feedback jobs using the same callback for both of them.
static void
_feedback_job_msg_cb(
void *data,
Ecore_Thread *th,
void *msg_data)
{
App_Data *ad = data;
App_Msg *msg = msg_data;
char *str;
The light job running out of the pool will let us know when we can exit our program.
if (msg->all_done)
{
free(msg);
return;
}
void ecore_main_loop_quit(void)
Quits the main loop once all the events currently on the queue have been processed.
Definition: ecore_main.c:1321
Next comes the handling of data sent from the actual worker threads, always remembering that the data belongs to us now, and not the thread, so it's our responsibility to free it.
_print_status();
if (!msg->list)
printf("Received an empty list from thread %p\n", th);
else
{
int i = 0;
printf("Received %d elements from threads %p (printing first 5):\n",
{
if (i <= 5)
printf("\t%s\n", str);
free(str);
i++;
}
static unsigned int eina_list_count(const Eina_List *list)
Gets the count of the number of items in a list.
}
Last, the condition to exit is given by how many messages we want to handle, so we need to count them and inform the condition checking thread that the value changed.
ad->msgs_received++;
free(msg);
}
static Eina_Lock_Result eina_lock_take(Eina_Lock *mutex)
Attempts to take a lock.
static Eina_Bool eina_condition_signal(Eina_Condition *cond)
Signals a thread waiting for a condition.
When a thread finishes its job or gets canceled, the main loop is notified through the callbacks set when creating the task. In this case, we just print what happen and keep track of one of them used to exemplify canceling. Here we are pretending one of our short jobs has a timeout, so if it doesn't finish before a timer is triggered, it will be canceled.
static void
{
App_Data *ad = data;
printf("Normal termination for thread %p.\n", th);
if (th == ad->thread_3)
ad->thread_3 = NULL;
}
static void
{
App_Data *ad = data;
printf("Thread %p got cancelled.\n", th);
if (th == ad->thread_3)
ad->thread_3 = NULL;
}
_cancel_timer_cb(void *data)
unsigned char Eina_Bool
Type to mimic a boolean.
Definition: eina_types.h:527
{
App_Data *ad = data;
}
The main function does some setup that includes reading parameters from the command line to change its behaviour and test different results. These are:
- -t <some_num> maximum number of threads to run at the same time.
- -p <some_path> adds
some_path
to the list used by the feedback jobs. This parameter can be used multiple times.
- -m <some_num> the number of messages to process before the program is signalled to exit.
Skipping some bits, we init Ecore and our application data.
printf("Initial max threads: %d\n", i);
memset(&appdata, 0, sizeof(App_Data));
appdata.max_msgs = 1;
EAPI int ecore_init(void)
Sets up connections, signal handlers, sockets etc.
Definition: ecore.c:230
int ecore_thread_max_get(void)
Gets the maximum number of threads that can run simultaneously.
Definition: ecore_thread.c:1205
If any paths for the feedback jobs were given, we use them, otherwise we fallback to some defaults. Always initializing the proper mutexes used by the threaded job.
if (!path_list)
{
Feedback_Thread_Data *ftd;
ftd = calloc(1, sizeof(Feedback_Thread_Data));
ftd->name = strdup("data0");
#ifdef _WIN32
ftd->base = strdup("c:/windows/System32");
#else
ftd->base = strdup("/usr/bin");
#endif
ftd = calloc(1, sizeof(Feedback_Thread_Data));
ftd->name = strdup("data1");
#ifdef _WIN32
ftd->base = strdup("c:/windows/Fonts");
#else
ftd->base = strdup("/usr/lib");
#endif
ftd = calloc(1, sizeof(Feedback_Thread_Data));
ftd->name = strdup("data2");
#ifdef _WIN32
ftd->base = strdup("c:/windows/Help");
#else
ftd->base = strdup("/usr/lib");
#endif
}
else
{
Feedback_Thread_Data *ftd;
char *str;
i = 0;
Eina_Bool ecore_thread_global_data_add(const char *key, void *value, Eina_Free_Cb cb, Eina_Bool direct)
Adds some data to a hash shared by all threads.
Definition: ecore_thread.c:1373
static Eina_Bool eina_lock_new(Eina_Lock *mutex)
Initializes a new Eina_Lock.
#define EINA_TRUE
boolean value TRUE (numerical value 1)
Definition: eina_types.h:539
{
char buf[32];
snprintf(buf, sizeof(buf), "data%d", i);
ftd = calloc(1, sizeof(Feedback_Thread_Data));
ftd->name = strdup(buf);
ftd->base = strdup(str);
free(str);
i++;
}
}
Initialize the mutex needed for the condition checking thread
static Eina_Bool eina_condition_new(Eina_Condition *cond, Eina_Lock *mutex)
Initializes a new condition variable.
And start our tasks.
_thread_end_cb, _thread_cancel_cb, &appdata,
Ecore_Thread * ecore_thread_run(Ecore_Thread_Cb func_blocking, Ecore_Thread_Cb func_end, Ecore_Thread_Cb func_cancel, const void *data)
Schedules a task to run in a parallel thread to avoid locking the main loop.
Definition: ecore_thread.c:658
Ecore_Thread * ecore_thread_feedback_run(Ecore_Thread_Cb func_heavy, Ecore_Thread_Notify_Cb func_notify, Ecore_Thread_Cb func_end, Ecore_Thread_Cb func_cancel, const void *data, Eina_Bool try_no_queue)
Launches a thread to run a task that can talk back to the main thread.
Definition: ecore_thread.c:911
_thread_cancel_cb, &appdata);
_thread_end_cb, _thread_cancel_cb, &appdata,
To finalize, set a timer to cancel one of the tasks if it doesn't end before the timeout, one more timer for status report and get into the main loop. Once we are out, destroy our mutexes and finish the program.
Ecore_Timer * ecore_timer_add(double in, Ecore_Task_Cb func, const void *data)
Creates a timer to call the given function in the given period of time.
Definition: ecore_timer.c:189
_print_status();
return 0;
}
EAPI int ecore_shutdown(void)
Shuts down connections, signal handlers sockets etc.
Definition: ecore.c:371
void ecore_main_loop_begin(void)
Runs the application main loop.
Definition: ecore_main.c:1311
static void eina_condition_free(Eina_Condition *cond)
Deallocates a condition variable.
static void eina_lock_free(Eina_Lock *mutex)
Deallocates an Eina_Lock.