| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the COPYING file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| // ----------------------------------------------------------------------------- |
| // |
| // Multi-threaded worker |
| // |
| // Author: Skal (pascal.massimino@gmail.com) |
| |
| |
| #if defined(STARBOARD) |
| #include "starboard/log.h" |
| #include "starboard/memory.h" |
| #else |
| #include <string.h> // for SbMemorySet() |
| #endif |
| #include "./thread.h" |
| |
| #if defined(__cplusplus) || defined(c_plusplus) |
| extern "C" { |
| #endif |
| |
| #ifdef WEBP_USE_THREAD |
| |
| #if defined(_WIN32) |
| |
| //------------------------------------------------------------------------------ |
| // simplistic pthread emulation layer |
| |
| #include <process.h> |
| |
| // _beginthreadex requires __stdcall |
| #define THREADFN unsigned int __stdcall |
| #define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val) |
| |
| static int pthread_create(pthread_t* const thread, const void* attr, |
| unsigned int (__stdcall *start)(void*), void* arg) { |
| (void)attr; |
| *thread = (pthread_t)_beginthreadex(NULL, /* void *security */ |
| 0, /* unsigned stack_size */ |
| start, |
| arg, |
| 0, /* unsigned initflag */ |
| NULL); /* unsigned *thrdaddr */ |
| if (*thread == NULL) return 1; |
| SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL); |
| return 0; |
| } |
| |
| static int pthread_join(pthread_t thread, void** value_ptr) { |
| (void)value_ptr; |
| return (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0 || |
| CloseHandle(thread) == 0); |
| } |
| |
| // Mutex |
| static int pthread_mutex_init(pthread_mutex_t* const mutex, void* mutexattr) { |
| (void)mutexattr; |
| InitializeCriticalSection(mutex); |
| return 0; |
| } |
| |
| static int pthread_mutex_lock(pthread_mutex_t* const mutex) { |
| EnterCriticalSection(mutex); |
| return 0; |
| } |
| |
| static int pthread_mutex_unlock(pthread_mutex_t* const mutex) { |
| LeaveCriticalSection(mutex); |
| return 0; |
| } |
| |
| static int pthread_mutex_destroy(pthread_mutex_t* const mutex) { |
| DeleteCriticalSection(mutex); |
| return 0; |
| } |
| |
| // Condition |
| static int pthread_cond_destroy(pthread_cond_t* const condition) { |
| int ok = 1; |
| ok &= (CloseHandle(condition->waiting_sem_) != 0); |
| ok &= (CloseHandle(condition->received_sem_) != 0); |
| ok &= (CloseHandle(condition->signal_event_) != 0); |
| return !ok; |
| } |
| |
| static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) { |
| (void)cond_attr; |
| condition->waiting_sem_ = CreateSemaphore(NULL, 0, 1, NULL); |
| condition->received_sem_ = CreateSemaphore(NULL, 0, 1, NULL); |
| condition->signal_event_ = CreateEvent(NULL, FALSE, FALSE, NULL); |
| if (condition->waiting_sem_ == NULL || |
| condition->received_sem_ == NULL || |
| condition->signal_event_ == NULL) { |
| pthread_cond_destroy(condition); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int pthread_cond_signal(pthread_cond_t* const condition) { |
| int ok = 1; |
| if (WaitForSingleObject(condition->waiting_sem_, 0) == WAIT_OBJECT_0) { |
| // a thread is waiting in pthread_cond_wait: allow it to be notified |
| ok = SetEvent(condition->signal_event_); |
| // wait until the event is consumed so the signaler cannot consume |
| // the event via its own pthread_cond_wait. |
| ok &= (WaitForSingleObject(condition->received_sem_, INFINITE) != |
| WAIT_OBJECT_0); |
| } |
| return !ok; |
| } |
| |
| static int pthread_cond_wait(pthread_cond_t* const condition, |
| pthread_mutex_t* const mutex) { |
| int ok; |
| // note that there is a consumer available so the signal isn't dropped in |
| // pthread_cond_signal |
| if (!ReleaseSemaphore(condition->waiting_sem_, 1, NULL)) |
| return 1; |
| // now unlock the mutex so pthread_cond_signal may be issued |
| pthread_mutex_unlock(mutex); |
| ok = (WaitForSingleObject(condition->signal_event_, INFINITE) == |
| WAIT_OBJECT_0); |
| ok &= ReleaseSemaphore(condition->received_sem_, 1, NULL); |
| pthread_mutex_lock(mutex); |
| return !ok; |
| } |
| |
| #else // _WIN32 |
| # define THREADFN void* |
| # define THREAD_RETURN(val) val |
| #endif |
| |
| //------------------------------------------------------------------------------ |
| |
| static THREADFN WebPWorkerThreadLoop(void *ptr) { // thread loop |
| WebPWorker* const worker = (WebPWorker*)ptr; |
| int done = 0; |
| while (!done) { |
| pthread_mutex_lock(&worker->mutex_); |
| while (worker->status_ == OK) { // wait in idling mode |
| pthread_cond_wait(&worker->condition_, &worker->mutex_); |
| } |
| if (worker->status_ == WORK) { |
| if (worker->hook) { |
| worker->had_error |= !worker->hook(worker->data1, worker->data2); |
| } |
| worker->status_ = OK; |
| } else if (worker->status_ == NOT_OK) { // finish the worker |
| done = 1; |
| } |
| // signal to the main thread that we're done (for Sync()) |
| pthread_cond_signal(&worker->condition_); |
| pthread_mutex_unlock(&worker->mutex_); |
| } |
| return THREAD_RETURN(NULL); // Thread is finished |
| } |
| |
| // main thread state control |
| static void WebPWorkerChangeState(WebPWorker* const worker, |
| WebPWorkerStatus new_status) { |
| // no-op when attempting to change state on a thread that didn't come up |
| if (worker->status_ < OK) return; |
| |
| pthread_mutex_lock(&worker->mutex_); |
| // wait for the worker to finish |
| while (worker->status_ != OK) { |
| pthread_cond_wait(&worker->condition_, &worker->mutex_); |
| } |
| // assign new status and release the working thread if needed |
| if (new_status != OK) { |
| worker->status_ = new_status; |
| pthread_cond_signal(&worker->condition_); |
| } |
| pthread_mutex_unlock(&worker->mutex_); |
| } |
| |
| #endif |
| |
| //------------------------------------------------------------------------------ |
| |
| void WebPWorkerInit(WebPWorker* const worker) { |
| SbMemorySet(worker, 0, sizeof(*worker)); |
| worker->status_ = NOT_OK; |
| } |
| |
| int WebPWorkerSync(WebPWorker* const worker) { |
| #ifdef WEBP_USE_THREAD |
| WebPWorkerChangeState(worker, OK); |
| #endif |
| SB_DCHECK(worker->status_ <= OK); |
| return !worker->had_error; |
| } |
| |
| int WebPWorkerReset(WebPWorker* const worker) { |
| int ok = 1; |
| worker->had_error = 0; |
| if (worker->status_ < OK) { |
| #ifdef WEBP_USE_THREAD |
| if (pthread_mutex_init(&worker->mutex_, NULL) || |
| pthread_cond_init(&worker->condition_, NULL)) { |
| return 0; |
| } |
| pthread_mutex_lock(&worker->mutex_); |
| ok = !pthread_create(&worker->thread_, NULL, WebPWorkerThreadLoop, worker); |
| if (ok) worker->status_ = OK; |
| pthread_mutex_unlock(&worker->mutex_); |
| #else |
| worker->status_ = OK; |
| #endif |
| } else if (worker->status_ > OK) { |
| ok = WebPWorkerSync(worker); |
| } |
| SB_DCHECK(!ok || (worker->status_ == OK)); |
| return ok; |
| } |
| |
| void WebPWorkerLaunch(WebPWorker* const worker) { |
| #ifdef WEBP_USE_THREAD |
| WebPWorkerChangeState(worker, WORK); |
| #else |
| if (worker->hook) |
| worker->had_error |= !worker->hook(worker->data1, worker->data2); |
| #endif |
| } |
| |
| void WebPWorkerEnd(WebPWorker* const worker) { |
| if (worker->status_ >= OK) { |
| #ifdef WEBP_USE_THREAD |
| WebPWorkerChangeState(worker, NOT_OK); |
| pthread_join(worker->thread_, NULL); |
| pthread_mutex_destroy(&worker->mutex_); |
| pthread_cond_destroy(&worker->condition_); |
| #else |
| worker->status_ = NOT_OK; |
| #endif |
| } |
| SB_DCHECK(worker->status_ == NOT_OK); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| #if defined(__cplusplus) || defined(c_plusplus) |
| } // extern "C" |
| #endif |