summaryrefslogtreecommitdiff
path: root/gold/workqueue.cc
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@google.com>2007-11-22 00:05:51 +0000
committerIan Lance Taylor <iant@google.com>2007-11-22 00:05:51 +0000
commitc79126688f8211ab17a893c5e80b09811d424fc1 (patch)
tree23a727f6718dc203a4e3b9ef1575e8a10b6f0d80 /gold/workqueue.cc
parent06d063c072d0e247751535bc5e394aa7b8be3b0f (diff)
downloadbinutils-gdb-c79126688f8211ab17a893c5e80b09811d424fc1.tar.gz
Add threading support.
Diffstat (limited to 'gold/workqueue.cc')
-rw-r--r--gold/workqueue.cc213
1 files changed, 132 insertions, 81 deletions
diff --git a/gold/workqueue.cc b/gold/workqueue.cc
index 95c14ce5a85..018c96bdbd2 100644
--- a/gold/workqueue.cc
+++ b/gold/workqueue.cc
@@ -22,11 +22,9 @@
#include "gold.h"
-#ifdef ENABLE_THREADS
-#include <pthread.h>
-#endif
-
+#include "debug.h"
#include "workqueue.h"
+#include "workqueue-internal.h"
namespace gold
{
@@ -145,38 +143,6 @@ Task_block_token::~Task_block_token()
}
}
-// The Workqueue_runner abstract class.
-
-class Workqueue_runner
-{
- public:
- Workqueue_runner(Workqueue* workqueue)
- : workqueue_(workqueue)
- { }
- virtual ~Workqueue_runner()
- { }
-
- // Run a task. This is always called in the main thread.
- virtual void
- run(Task*, Task_locker*) = 0;
-
- // Set the number of threads to use. This is ignored when not using
- // threads.
- virtual void
- set_thread_count(int) = 0;
-
- protected:
- // This is called by an implementation when a task is completed.
- void completed(Task* t, Task_locker* tl)
- { this->workqueue_->completed(t, tl); }
-
- Workqueue* get_workqueue() const
- { return this->workqueue_; }
-
- private:
- Workqueue* workqueue_;
-};
-
// The simple single-threaded implementation of Workqueue_runner.
class Workqueue_runner_single : public Workqueue_runner
@@ -212,12 +178,15 @@ Workqueue_runner_single::set_thread_count(int thread_count)
Workqueue::Workqueue(const General_options& options)
: tasks_lock_(),
+ first_tasks_(),
tasks_(),
completed_lock_(),
completed_(),
running_(0),
+ queued_(0),
completed_condvar_(this->completed_lock_),
- cleared_blockers_(0)
+ cleared_blockers_(0),
+ desired_thread_count_(1)
{
bool threads = options.threads();
#ifndef ENABLE_THREADS
@@ -226,11 +195,18 @@ Workqueue::Workqueue(const General_options& options)
if (!threads)
this->runner_ = new Workqueue_runner_single(this);
else
- gold_unreachable();
+ {
+#ifdef ENABLE_THREADS
+ this->runner_ = new Workqueue_runner_threadpool(this);
+#else
+ gold_unreachable();
+#endif
+ }
}
Workqueue::~Workqueue()
{
+ gold_assert(this->first_tasks_.empty());
gold_assert(this->tasks_.empty());
gold_assert(this->completed_.empty());
gold_assert(this->running_ == 0);
@@ -241,8 +217,14 @@ Workqueue::~Workqueue()
void
Workqueue::queue(Task* t)
{
- Hold_lock hl(this->tasks_lock_);
- this->tasks_.push_back(t);
+ {
+ Hold_lock hl(this->tasks_lock_);
+ this->tasks_.push_back(t);
+ }
+ {
+ Hold_lock hl(this->completed_lock_);
+ ++this->queued_;
+ }
}
// Add a task to the front of the queue.
@@ -250,8 +232,14 @@ Workqueue::queue(Task* t)
void
Workqueue::queue_front(Task* t)
{
- Hold_lock hl(this->tasks_lock_);
- this->tasks_.push_front(t);
+ {
+ Hold_lock hl(this->tasks_lock_);
+ this->first_tasks_.push_front(t);
+ }
+ {
+ Hold_lock hl(this->completed_lock_);
+ ++this->queued_;
+ }
}
// Clear the list of completed tasks. Return whether we cleared
@@ -277,48 +265,36 @@ Workqueue::clear_completed()
// a blocker.
Task*
-Workqueue::find_runnable(Task_list& tasks, bool* all_blocked)
+Workqueue::find_runnable(Task_list* tasks, bool* all_blocked)
{
- Task* tlast = tasks.back();
+ Task* tlast = tasks->back();
*all_blocked = true;
- while (true)
+ Task* t;
+ do
{
- Task* t = tasks.front();
- tasks.pop_front();
+ t = tasks->front();
+ tasks->pop_front();
Task::Is_runnable_type is_runnable = t->is_runnable(this);
if (is_runnable == Task::IS_RUNNABLE)
- return t;
-
- if (is_runnable != Task::IS_BLOCKED)
- *all_blocked = false;
-
- tasks.push_back(t);
-
- if (t == tlast)
{
- // We couldn't find any runnable task. If there are any
- // completed tasks, free their locks and try again.
-
{
- Hold_lock hl2(this->completed_lock_);
-
- if (!this->clear_completed())
- {
- // There had better be some tasks running, or we will
- // never find a runnable task.
- gold_assert(this->running_ > 0);
-
- // We couldn't find any runnable tasks, and we
- // couldn't release any locks.
- return NULL;
- }
+ Hold_lock hl(this->completed_lock_);
+ --this->queued_;
}
- // We're going around again, so recompute ALL_BLOCKED.
- *all_blocked = true;
+ return t;
}
+
+ if (is_runnable != Task::IS_BLOCKED)
+ *all_blocked = false;
+
+ tasks->push_back(t);
}
+ while (t != tlast);
+
+ // We couldn't find any runnable task.
+ return NULL;
}
// Process all the tasks on the workqueue. This is the main loop in
@@ -334,28 +310,56 @@ Workqueue::process()
bool empty;
bool all_blocked;
+ // Don't start more tasks than desired.
+ {
+ Hold_lock hl(this->completed_lock_);
+
+ this->clear_completed();
+ while (this->running_ >= this->desired_thread_count_)
+ {
+ this->completed_condvar_.wait();
+ this->clear_completed();
+ }
+ }
+
{
Hold_lock hl(this->tasks_lock_);
- if (this->tasks_.empty())
+ bool first_empty;
+ bool all_blocked_first;
+ if (this->first_tasks_.empty())
{
t = NULL;
empty = true;
- all_blocked = false;
+ first_empty = true;
+ all_blocked_first = false;
}
else
{
- t = this->find_runnable(this->tasks_, &all_blocked);
+ t = this->find_runnable(&this->first_tasks_, &all_blocked_first);
empty = false;
+ first_empty = false;
+ }
+
+ if (t == NULL)
+ {
+ if (this->tasks_.empty())
+ all_blocked = false;
+ else
+ {
+ t = this->find_runnable(&this->tasks_, &all_blocked);
+ if (!first_empty && !all_blocked_first)
+ all_blocked = false;
+ empty = false;
+ }
}
}
// If T != NULL, it is a task we can run.
// If T == NULL && empty, then there are no tasks waiting to
- // be run at this level.
+ // be run.
// If T == NULL && !empty, then there tasks waiting to be
- // run at this level, but they are waiting for something to
- // unlock.
+ // run, but they are waiting for something to unlock.
if (t != NULL)
this->run(t);
@@ -371,10 +375,16 @@ Workqueue::process()
if (all_blocked)
{
this->cleared_blockers_ = 0;
+ int queued = this->queued_;
this->clear_completed();
- while (this->cleared_blockers_ == 0)
+ while (this->cleared_blockers_ == 0
+ && queued == this->queued_)
{
- gold_assert(this->running_ > 0);
+ if (this->running_ <= 0)
+ {
+ this->show_queued_tasks();
+ gold_unreachable();
+ }
this->completed_condvar_.wait();
this->clear_completed();
}
@@ -416,7 +426,12 @@ Workqueue::process()
void
Workqueue::run(Task* t)
{
- ++this->running_;
+ gold_debug(DEBUG_TASK, "starting task %s", t->name().c_str());
+
+ {
+ Hold_lock hl(this->completed_lock_);
+ ++this->running_;
+ }
this->runner_->run(t, t->locks(this));
}
@@ -427,6 +442,8 @@ Workqueue::run(Task* t)
void
Workqueue::completed(Task* t, Task_locker* tl)
{
+ gold_debug(DEBUG_TASK, "completed task %s", t->name().c_str());
+
{
Hold_lock hl(this->completed_lock_);
gold_assert(this->running_ > 0);
@@ -434,6 +451,7 @@ Workqueue::completed(Task* t, Task_locker* tl)
this->completed_.push_back(tl);
this->completed_condvar_.signal();
}
+
delete t;
}
@@ -452,7 +470,40 @@ Workqueue::cleared_blocker()
void
Workqueue::set_thread_count(int threads)
{
+ gold_assert(threads > 0);
+ this->desired_thread_count_ = threads;
this->runner_->set_thread_count(threads);
}
+// Dump the list of queued tasks and their current state, for
+// debugging purposes.
+
+void
+Workqueue::show_queued_tasks()
+{
+ fprintf(stderr, _("gold task queue:\n"));
+ Hold_lock hl(this->tasks_lock_);
+ for (Task_list::const_iterator p = this->tasks_.begin();
+ p != this->tasks_.end();
+ ++p)
+ {
+ fprintf(stderr, " %s ", (*p)->name().c_str());
+ switch ((*p)->is_runnable(this))
+ {
+ case Task::IS_RUNNABLE:
+ fprintf(stderr, "runnable");
+ break;
+ case Task::IS_BLOCKED:
+ fprintf(stderr, "blocked");
+ break;
+ case Task::IS_LOCKED:
+ fprintf(stderr, "locked");
+ break;
+ default:
+ gold_unreachable();
+ }
+ putc('\n', stderr);
+ }
+}
+
} // End namespace gold.