Changeset 66506 in webkit


Ignore:
Timestamp:
Aug 31, 2010 10:53:17 AM (14 years ago)
Author:
Adam Roben
Message:

Use the Windows thread pool instead of a dedicated thread for WorkQueue on Windows

WorkQueue now uses ::RegisterWaitForSingleObject to find out when work
items need to be executed. This causes Windows to wait on the objects
on a thread pool wait thread, and then to spawn a thread pool worker
thread when an object is signaled. This is more efficient than using
our own dedicated thread to perform the wait and the work because
multiple WorkQueues (and even other parts of WebKit or other modules)
can all share the same wait thread and worker thread pool.

Each time WorkQueue::m_performWorkEvent or any handle in
WorkQueue::m_handles is signaled, a worker thread will be spawned.
To maintain WorkQueue's serial nature, only one worker thread is
allowed to perform work items at a time. (The worker thread that is
actually performing work items is called the queue's "work thread".)
To accomplish this, worker threads must register as the queue's work
thread before performing work items.
WorkQueue::m_isWorkThreadRegistered is used as an atomic guard to make
sure that only one worker thread is registered at a time.

Fixes <http://webkit.org/b/43150> <rdar://problem/8247280>.

Reviewed by Anders Carlsson.

  • Platform/WorkQueue.h:
    • Added the WorkItemWin class, which is used to wrap WorkItems for WorkQueue's Windows implementation
    • Changed m_workItemQueue and m_handles to hold RefPtr<WorkItemWin>s
    • Replaced "work queue thread"-related members with new members that handle our thread pool code
  • Platform/win/WorkQueueWin.cpp:

(WorkQueue::WorkItemWin::WorkItemWin):
(WorkQueue::WorkItemWin::create):
Added simple constructor/creator.

(WorkQueue::handleCallback): Added. This function is called whenever a
handle in WorkQueue::m_handles is signaled. We add the WorkItemWin
that corresponds to the handle (passed via the context parameter) to
the work item queue, then try to register as the work thread and
perform any queued work. If another thread is already registered as
the work thread, we just exit and let that thread handle the work we
queued.
(WorkQueue::registerHandle): Changed to wrap the WorkItem in a
WorkItemWin, and to use ::RegisterWaitForSingleObject to wait on the
handle.
(WorkQueue::eventCallback): Added. This function is called whenever
m_performWorkEvent is signaled. We try to register as the work thread
and perfom any queued work. If another thread is already registered as
the work thread, we just exit and let that thread handle the work.
(WorkQueue::performWorkOnRegisteredWorkThread): Added. Performs any
queued work in a loop until either the queue becomes invalid or no
work is left to perform. Unregisters as the work thread before exiting
so that other threads can perform work in the future.
(WorkQueue::platformInitialize): Added initialization of
m_isWorkThreadRegistered. Replaced code to spawn the old work queue
thread with a call to ::RegisterWaitForSingleObject so that a worker
thread from the thread pool will be spawned when m_performWorkEvent is
signaled.
(WorkQueue::tryRegisterAsWorkThread): Added. Attempts an atomic
compare-and-swap to change m_isWorkThreadRegistered from 0 to 1. If
sucessful, we return true to indicate that this thread is now
registered as the work thread.
(WorkQueue::unregisterAsWorkThread): Added. Uses an atomic
compare-and-swap to change m_isWorkThreadRegistered back from 1 to 0.
(WorkQueue::scheduleWork): Changed to wrap the WorkItem in a
WorkItemWin. Also added an optimization to avoid signaling
m_performWorkEvent when a work thread is already performing work, as
it will pick up the item we just queued without us having to do
anything.

Location:
trunk/WebKit2
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebKit2/ChangeLog

    r66495 r66506  
     12010-08-27  Adam Roben  <aroben@apple.com>
     2
     3        Use the Windows thread pool instead of a dedicated thread for
     4        WorkQueue on Windows
     5
     6        WorkQueue now uses ::RegisterWaitForSingleObject to find out when work
     7        items need to be executed. This causes Windows to wait on the objects
     8        on a thread pool wait thread, and then to spawn a thread pool worker
     9        thread when an object is signaled. This is more efficient than using
     10        our own dedicated thread to perform the wait and the work because
     11        multiple WorkQueues (and even other parts of WebKit or other modules)
     12        can all share the same wait thread and worker thread pool.
     13
     14        Each time WorkQueue::m_performWorkEvent or any handle in
     15        WorkQueue::m_handles is signaled, a worker thread will be spawned.
     16        To maintain WorkQueue's serial nature, only one worker thread is
     17        allowed to perform work items at a time. (The worker thread that is
     18        actually performing work items is called the queue's "work thread".)
     19        To accomplish this, worker threads must register as the queue's work
     20        thread before performing work items.
     21        WorkQueue::m_isWorkThreadRegistered is used as an atomic guard to make
     22        sure that only one worker thread is registered at a time.
     23       
     24        Fixes <http://webkit.org/b/43150> <rdar://problem/8247280>.
     25
     26        Reviewed by Anders Carlsson.
     27
     28        * Platform/WorkQueue.h:
     29          - Added the WorkItemWin class, which is used to wrap WorkItems for
     30            WorkQueue's Windows implementation
     31          - Changed m_workItemQueue and m_handles to hold
     32            RefPtr<WorkItemWin>s
     33          - Replaced "work queue thread"-related members with new members that
     34            handle our thread pool code
     35
     36        * Platform/win/WorkQueueWin.cpp:
     37        (WorkQueue::WorkItemWin::WorkItemWin):
     38        (WorkQueue::WorkItemWin::create):
     39        Added simple constructor/creator.
     40
     41        (WorkQueue::handleCallback): Added. This function is called whenever a
     42        handle in WorkQueue::m_handles is signaled. We add the WorkItemWin
     43        that corresponds to the handle (passed via the context parameter) to
     44        the work item queue, then try to register as the work thread and
     45        perform any queued work. If another thread is already registered as
     46        the work thread, we just exit and let that thread handle the work we
     47        queued.
     48        (WorkQueue::registerHandle): Changed to wrap the WorkItem in a
     49        WorkItemWin, and to use ::RegisterWaitForSingleObject to wait on the
     50        handle.
     51        (WorkQueue::eventCallback): Added. This function is called whenever
     52        m_performWorkEvent is signaled. We try to register as the work thread
     53        and perfom any queued work. If another thread is already registered as
     54        the work thread, we just exit and let that thread handle the work.
     55        (WorkQueue::performWorkOnRegisteredWorkThread): Added. Performs any
     56        queued work in a loop until either the queue becomes invalid or no
     57        work is left to perform. Unregisters as the work thread before exiting
     58        so that other threads can perform work in the future.
     59        (WorkQueue::platformInitialize): Added initialization of
     60        m_isWorkThreadRegistered. Replaced code to spawn the old work queue
     61        thread with a call to ::RegisterWaitForSingleObject so that a worker
     62        thread from the thread pool will be spawned when m_performWorkEvent is
     63        signaled.
     64        (WorkQueue::tryRegisterAsWorkThread): Added. Attempts an atomic
     65        compare-and-swap to change m_isWorkThreadRegistered from 0 to 1. If
     66        sucessful, we return true to indicate that this thread is now
     67        registered as the work thread.
     68        (WorkQueue::unregisterAsWorkThread): Added. Uses an atomic
     69        compare-and-swap to change m_isWorkThreadRegistered back from 1 to 0.
     70        (WorkQueue::scheduleWork): Changed to wrap the WorkItem in a
     71        WorkItemWin. Also added an optimization to avoid signaling
     72        m_performWorkEvent when a work thread is already performing work, as
     73        it will pick up the item we just queued without us having to do
     74        anything.
     75
    1762010-08-31  Csaba Osztrogonác  <ossy@webkit.org>
    277
  • trunk/WebKit2/Platform/WorkQueue.h

    r63148 r66506  
    3636#include <wtf/HashMap.h>
    3737#include <wtf/PassOwnPtr.h>
     38#include <wtf/RefCounted.h>
    3839#include <wtf/Threading.h>
    3940#include <wtf/Vector.h>
     
    9394#endif
    9495#elif PLATFORM(WIN)
    95     static void* workQueueThreadBody(void*);
    96     void workQueueThreadBody();
    97     void performWork();
     96    class WorkItemWin : public RefCounted<WorkItemWin> {
     97    public:
     98        static PassRefPtr<WorkItemWin> create(PassOwnPtr<WorkItem>, WorkQueue*);
    9899
    99     ThreadIdentifier m_workQueueThread;
     100        WorkItem* item() const { return m_item.get(); }
     101        WorkQueue* queue() const { return m_queue; }
     102
     103    private:
     104        WorkItemWin(PassOwnPtr<WorkItem>, WorkQueue*);
     105
     106        OwnPtr<WorkItem> m_item;
     107        WorkQueue* m_queue;
     108    };
     109
     110    static void CALLBACK handleCallback(void* context, BOOLEAN timerOrWaitFired);
     111    static void CALLBACK eventCallback(void* context, BOOLEAN timerOrWaitFired);
     112
     113    bool tryRegisterAsWorkThread();
     114    void unregisterAsWorkThread();
     115    void performWorkOnRegisteredWorkThread();
    100116
    101117    HANDLE m_performWorkEvent;
    102118
     119    volatile LONG m_isWorkThreadRegistered;
     120
    103121    Mutex m_workItemQueueLock;
    104     Vector<WorkItem*> m_workItemQueue;
     122    Vector<RefPtr<WorkItemWin> > m_workItemQueue;
    105123
    106124    Mutex m_handlesLock;
    107     HashMap<HANDLE, WorkItem*> m_handles;
     125    HashMap<HANDLE, RefPtr<WorkItemWin> > m_handles;
    108126#elif PLATFORM(QT)
    109127    class WorkItemQt;
  • trunk/WebKit2/Platform/win/WorkQueueWin.cpp

    r63912 r66506  
    2626#include "WorkQueue.h"
    2727
    28 #include <process.h>
    2928#include <wtf/Threading.h>
     29
     30inline WorkQueue::WorkItemWin::WorkItemWin(PassOwnPtr<WorkItem> item, WorkQueue* queue)
     31    : m_item(item)
     32    , m_queue(queue)
     33{
     34}
     35
     36PassRefPtr<WorkQueue::WorkItemWin> WorkQueue::WorkItemWin::create(PassOwnPtr<WorkItem> item, WorkQueue* queue)
     37{
     38    return adoptRef(new WorkItemWin(item, queue));
     39}
     40
     41void WorkQueue::handleCallback(void* context, BOOLEAN timerOrWaitFired)
     42{
     43    ASSERT_ARG(context, context);
     44    ASSERT_ARG(timerOrWaitFired, !timerOrWaitFired);
     45
     46    WorkItemWin* item = static_cast<WorkItemWin*>(context);
     47    WorkQueue* queue = item->queue();
     48
     49    {
     50        MutexLocker lock(queue->m_workItemQueueLock);
     51        queue->m_workItemQueue.append(item);
     52
     53        // If no other thread is performing work, we can do it on this thread.
     54        if (!queue->tryRegisterAsWorkThread()) {
     55            // Some other thread is performing work. Since we hold the queue lock, we can be sure
     56            // that the work thread is not exiting due to an empty queue and will process the work
     57            // item we just added to it. If we weren't holding the lock we'd have to signal
     58            // m_performWorkEvent to make sure the work item got picked up.
     59            return;
     60        }
     61    }
     62
     63    queue->performWorkOnRegisteredWorkThread();
     64}
    3065
    3166void WorkQueue::registerHandle(HANDLE handle, PassOwnPtr<WorkItem> item)
    3267{
     68    RefPtr<WorkItemWin> itemWin = WorkItemWin::create(item, this);
     69
    3370    // Add the item.
    3471    {
    3572        MutexLocker locker(m_handlesLock);
    36         m_handles.set(handle, item.leakPtr());
     73        m_handles.set(handle, itemWin);
    3774    }
    3875
    39     // Set the work event.
    40     ::SetEvent(m_performWorkEvent);
     76    // FIXME: We need to hold onto waitHandle so that we can unregister the wait later.
     77    HANDLE waitHandle;
     78    if (!::RegisterWaitForSingleObject(&waitHandle, handle, handleCallback, itemWin.get(), INFINITE, WT_EXECUTEDEFAULT)) {
     79        DWORD error = ::GetLastError();
     80        ASSERT_NOT_REACHED();
     81    }
    4182}
    4283
    43 void* WorkQueue::workQueueThreadBody(void *context)
     84void WorkQueue::eventCallback(void* context, BOOLEAN timerOrWaitFired)
    4485{
    45     static_cast<WorkQueue*>(context)->workQueueThreadBody();
    46     return 0;
     86    ASSERT_ARG(context, context);
     87    ASSERT_ARG(timerOrWaitFired, !timerOrWaitFired);
     88
     89    WorkQueue* queue = static_cast<WorkQueue*>(context);
     90
     91    if (!queue->tryRegisterAsWorkThread())
     92        return;
     93
     94    queue->performWorkOnRegisteredWorkThread();
    4795}
    4896
    49 void WorkQueue::workQueueThreadBody()
     97void WorkQueue::performWorkOnRegisteredWorkThread()
    5098{
    51     while (true) {
    52         Vector<HANDLE> handles;
    53         {
    54             // Copy the handles to our handles vector.
    55             MutexLocker locker(m_handlesLock);
    56             copyKeysToVector(m_handles, handles);
     99    ASSERT(m_isWorkThreadRegistered);
     100
     101    bool isValid = true;
     102
     103    m_workItemQueueLock.lock();
     104
     105    while (isValid && !m_workItemQueue.isEmpty()) {
     106        Vector<RefPtr<WorkItemWin> > workItemQueue;
     107        m_workItemQueue.swap(workItemQueue);
     108
     109        // Allow more work to be scheduled while we're not using the queue directly.
     110        m_workItemQueueLock.unlock();
     111        for (size_t i = 0; i < workItemQueue.size(); ++i) {
     112            MutexLocker locker(m_isValidMutex);
     113            isValid = m_isValid;
     114            if (!isValid)
     115                break;
     116            workItemQueue[i]->item()->execute();
    57117        }
     118        m_workItemQueueLock.lock();
     119    }
    58120
    59         // Add the "perform work" event handle.
    60         handles.append(m_performWorkEvent);
     121    // One invariant we maintain is that any work scheduled while a work thread is registered will
     122    // be handled by that work thread. Unregister as the work thread while the queue lock is still
     123    // held so that no work can be scheduled while we're still registered.
     124    unregisterAsWorkThread();
    61125
    62         ASSERT(handles.size() <= MAXIMUM_WAIT_OBJECTS);
    63 
    64         // Now we wait.
    65         DWORD result = ::WaitForMultipleObjects(handles.size(), handles.data(), FALSE, INFINITE);
    66         if (result == WAIT_FAILED) {
    67             DWORD error = ::GetLastError();
    68             ASSERT_NOT_REACHED();
    69         }
    70 
    71         // The wait should never time out since we passed INFINITE for the timeout interval.
    72         ASSERT(result != WAIT_TIMEOUT);
    73         // We don't know how (or need) to handle abandoned mutexes yet.
    74         ASSERT(result < WAIT_ABANDONED_0 || result >= WAIT_ABANDONED_0 + handles.size());
    75 
    76         if (result == handles.size() - 1)
    77             performWork();
    78         else {
    79             // FIXME: If we ever decide to support unregistering handles we would need to copy the hash map.
    80             WorkItem* workItem;
    81             HANDLE handle = handles[result];
    82 
    83             {
    84                 MutexLocker locker(m_handlesLock);
    85                 workItem = m_handles.get(handle);
    86             }
    87 
    88             // Execute the work item.
    89             workItem->execute();
    90         }
    91 
    92         // Check if this queue is invalid.
    93         {
    94             MutexLocker locker(m_isValidMutex);
    95             if (!m_isValid)
    96                 break;
    97         }
    98     }
     126    m_workItemQueueLock.unlock();
    99127}
    100128
    101129void WorkQueue::platformInitialize(const char* name)
    102130{
     131    m_isWorkThreadRegistered = 0;
     132
    103133    // Create our event.
    104     m_performWorkEvent = ::CreateEvent(0, false, false, 0);
     134    m_performWorkEvent = ::CreateEventW(0, FALSE, FALSE, 0);
    105135
    106     m_workQueueThread = createThread(&WorkQueue::workQueueThreadBody, this, name);
     136    // FIXME: We need to hold onto waitHandle so that we can unregister the wait later.
     137    HANDLE waitHandle;
     138    if (!::RegisterWaitForSingleObject(&waitHandle, m_performWorkEvent, eventCallback, this, INFINITE, WT_EXECUTEDEFAULT)) {
     139        DWORD error = ::GetLastError();
     140        ASSERT_NOT_REACHED();
     141    }
     142}
     143
     144bool WorkQueue::tryRegisterAsWorkThread()
     145{
     146    LONG result = ::InterlockedCompareExchange(&m_isWorkThreadRegistered, 1, 0);
     147    ASSERT(!result || result == 1);
     148    return !result;
     149}
     150
     151void WorkQueue::unregisterAsWorkThread()
     152{
     153    LONG result = ::InterlockedCompareExchange(&m_isWorkThreadRegistered, 0, 1);
     154    ASSERT_UNUSED(result, result == 1);
    107155}
    108156
     
    117165{
    118166    MutexLocker locker(m_workItemQueueLock);
    119     m_workItemQueue.append(item.leakPtr());
    120167
    121     // Set the work event.
    122     ::SetEvent(m_performWorkEvent);
     168    m_workItemQueue.append(WorkItemWin::create(item, this));
     169
     170    // Signal our event so that work thread will perform the work we just added. As an optimization,
     171    // we avoid signaling the event if a work thread is already registered. This prevents multiple
     172    // work threads from being spawned in most cases. (Note that when a work thread has been spawned
     173    // but hasn't registered itself yet, m_isWorkThreadRegistered will be false and we'll end up
     174    // spawning a second work thread here. But work thread registration process will ensure that
     175    // only one thread actually ends up performing work.)
     176    if (!m_isWorkThreadRegistered)
     177        ::SetEvent(m_performWorkEvent);
    123178}
    124 
    125 void WorkQueue::performWork()
    126 {
    127     Vector<WorkItem*> workItemQueue;
    128     {
    129         MutexLocker locker(m_workItemQueueLock);
    130         m_workItemQueue.swap(workItemQueue);
    131     }
    132 
    133     for (size_t i = 0; i < workItemQueue.size(); ++i) {
    134         OwnPtr<WorkItem> item(workItemQueue[i]);
    135 
    136         MutexLocker locker(m_isValidMutex);
    137         if (m_isValid)
    138             item->execute();
    139     }
    140 }
Note: See TracChangeset for help on using the changeset viewer.