threaded pack-objects: Use condition variables for thread communication.
In the threaded pack-objects code the main thread and the worker threads must mutually signal that they have assigned a new pack of work or have completed their work, respectively. Previously, the code used mutexes that were locked in one thread and unlocked from a different thread, which is bogus (and happens to work on Linux). Here we rectify the implementation by using condition variables: There is one condition variable on which the main thread waits until a thread requests new work; and each worker thread has its own condition variable on which it waits until it is assigned new work or signaled to terminate. As a cleanup, the worker threads are spawned only after the initial work packages have been assigned. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> Acked-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
3eb2a15eb3
commit
50f22ada52
@ -1594,6 +1594,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
|
|||||||
|
|
||||||
#ifdef THREADED_DELTA_SEARCH
|
#ifdef THREADED_DELTA_SEARCH
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The main thread waits on the condition that (at least) one of the workers
|
||||||
|
* has stopped working (which is indicated in the .working member of
|
||||||
|
* struct thread_params).
|
||||||
|
* When a work thread has completed its work, it sets .working to 0 and
|
||||||
|
* signals the main thread and waits on the condition that .data_ready
|
||||||
|
* becomes 1.
|
||||||
|
*/
|
||||||
|
|
||||||
struct thread_params {
|
struct thread_params {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
struct object_entry **list;
|
struct object_entry **list;
|
||||||
@ -1601,37 +1610,50 @@ struct thread_params {
|
|||||||
unsigned remaining;
|
unsigned remaining;
|
||||||
int window;
|
int window;
|
||||||
int depth;
|
int depth;
|
||||||
|
int working;
|
||||||
|
int data_ready;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
pthread_cond_t cond;
|
||||||
unsigned *processed;
|
unsigned *processed;
|
||||||
};
|
};
|
||||||
|
|
||||||
static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
|
||||||
static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
static struct thread_params *data_requester;
|
|
||||||
|
|
||||||
static void *threaded_find_deltas(void *arg)
|
static void *threaded_find_deltas(void *arg)
|
||||||
{
|
{
|
||||||
struct thread_params *me = arg;
|
struct thread_params *me = arg;
|
||||||
|
|
||||||
for (;;) {
|
while (me->remaining) {
|
||||||
pthread_mutex_lock(&data_request);
|
|
||||||
data_requester = me;
|
|
||||||
pthread_mutex_unlock(&data_provider);
|
|
||||||
pthread_mutex_lock(&data_ready);
|
|
||||||
pthread_mutex_unlock(&data_request);
|
|
||||||
|
|
||||||
if (!me->remaining)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
find_deltas(me->list, &me->remaining,
|
find_deltas(me->list, &me->remaining,
|
||||||
me->window, me->depth, me->processed);
|
me->window, me->depth, me->processed);
|
||||||
|
|
||||||
|
progress_lock();
|
||||||
|
me->working = 0;
|
||||||
|
pthread_cond_signal(&progress_cond);
|
||||||
|
progress_unlock();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must not set ->data_ready before we wait on the
|
||||||
|
* condition because the main thread may have set it to 1
|
||||||
|
* before we get here. In order to be sure that new
|
||||||
|
* work is available if we see 1 in ->data_ready, it
|
||||||
|
* was initialized to 0 before this thread was spawned
|
||||||
|
* and we reset it to 0 right away.
|
||||||
|
*/
|
||||||
|
pthread_mutex_lock(&me->mutex);
|
||||||
|
while (!me->data_ready)
|
||||||
|
pthread_cond_wait(&me->cond, &me->mutex);
|
||||||
|
me->data_ready = 0;
|
||||||
|
pthread_mutex_unlock(&me->mutex);
|
||||||
}
|
}
|
||||||
|
/* leave ->working 1 so that this doesn't get more work assigned */
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
||||||
int window, int depth, unsigned *processed)
|
int window, int depth, unsigned *processed)
|
||||||
{
|
{
|
||||||
struct thread_params *target, p[delta_search_threads];
|
struct thread_params p[delta_search_threads];
|
||||||
int i, ret, active_threads = 0;
|
int i, ret, active_threads = 0;
|
||||||
|
|
||||||
if (delta_search_threads <= 1) {
|
if (delta_search_threads <= 1) {
|
||||||
@ -1639,49 +1661,42 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_lock(&data_provider);
|
/* Partition the work amongst work threads. */
|
||||||
pthread_mutex_lock(&data_ready);
|
|
||||||
|
|
||||||
/* Start work threads. */
|
|
||||||
for (i = 0; i < delta_search_threads; i++) {
|
|
||||||
p[i].window = window;
|
|
||||||
p[i].depth = depth;
|
|
||||||
p[i].processed = processed;
|
|
||||||
p[i].remaining = 0;
|
|
||||||
ret = pthread_create(&p[i].thread, NULL,
|
|
||||||
threaded_find_deltas, &p[i]);
|
|
||||||
if (ret)
|
|
||||||
die("unable to create thread: %s", strerror(ret));
|
|
||||||
active_threads++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Then partition the work amongst them. */
|
|
||||||
for (i = 0; i < delta_search_threads; i++) {
|
for (i = 0; i < delta_search_threads; i++) {
|
||||||
unsigned sub_size = list_size / (delta_search_threads - i);
|
unsigned sub_size = list_size / (delta_search_threads - i);
|
||||||
|
|
||||||
pthread_mutex_lock(&data_provider);
|
p[i].window = window;
|
||||||
target = data_requester;
|
p[i].depth = depth;
|
||||||
if (!sub_size) {
|
p[i].processed = processed;
|
||||||
pthread_mutex_unlock(&data_ready);
|
p[i].working = 1;
|
||||||
pthread_join(target->thread, NULL);
|
p[i].data_ready = 0;
|
||||||
active_threads--;
|
pthread_mutex_init(&p[i].mutex, NULL);
|
||||||
continue;
|
pthread_cond_init(&p[i].cond, NULL);
|
||||||
}
|
|
||||||
|
|
||||||
/* try to split chunks on "path" boundaries */
|
/* try to split chunks on "path" boundaries */
|
||||||
while (sub_size < list_size && list[sub_size]->hash &&
|
while (sub_size < list_size && list[sub_size]->hash &&
|
||||||
list[sub_size]->hash == list[sub_size-1]->hash)
|
list[sub_size]->hash == list[sub_size-1]->hash)
|
||||||
sub_size++;
|
sub_size++;
|
||||||
|
|
||||||
target->list = list;
|
p[i].list = list;
|
||||||
target->list_size = sub_size;
|
p[i].list_size = sub_size;
|
||||||
target->remaining = sub_size;
|
p[i].remaining = sub_size;
|
||||||
pthread_mutex_unlock(&data_ready);
|
|
||||||
|
|
||||||
list += sub_size;
|
list += sub_size;
|
||||||
list_size -= sub_size;
|
list_size -= sub_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Start work threads. */
|
||||||
|
for (i = 0; i < delta_search_threads; i++) {
|
||||||
|
if (!p[i].list_size)
|
||||||
|
continue;
|
||||||
|
ret = pthread_create(&p[i].thread, NULL,
|
||||||
|
threaded_find_deltas, &p[i]);
|
||||||
|
if (ret)
|
||||||
|
die("unable to create thread: %s", strerror(ret));
|
||||||
|
active_threads++;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now let's wait for work completion. Each time a thread is done
|
* Now let's wait for work completion. Each time a thread is done
|
||||||
* with its work, we steal half of the remaining work from the
|
* with its work, we steal half of the remaining work from the
|
||||||
@ -1690,13 +1705,21 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
|||||||
* until the remaining object list segments are simply too short
|
* until the remaining object list segments are simply too short
|
||||||
* to be worth splitting anymore.
|
* to be worth splitting anymore.
|
||||||
*/
|
*/
|
||||||
do {
|
while (active_threads) {
|
||||||
|
struct thread_params *target = NULL;
|
||||||
struct thread_params *victim = NULL;
|
struct thread_params *victim = NULL;
|
||||||
unsigned sub_size = 0;
|
unsigned sub_size = 0;
|
||||||
pthread_mutex_lock(&data_provider);
|
|
||||||
target = data_requester;
|
|
||||||
|
|
||||||
progress_lock();
|
progress_lock();
|
||||||
|
for (;;) {
|
||||||
|
for (i = 0; !target && i < delta_search_threads; i++)
|
||||||
|
if (!p[i].working)
|
||||||
|
target = &p[i];
|
||||||
|
if (target)
|
||||||
|
break;
|
||||||
|
pthread_cond_wait(&progress_cond, &progress_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < delta_search_threads; i++)
|
for (i = 0; i < delta_search_threads; i++)
|
||||||
if (p[i].remaining > 2*window &&
|
if (p[i].remaining > 2*window &&
|
||||||
(!victim || victim->remaining < p[i].remaining))
|
(!victim || victim->remaining < p[i].remaining))
|
||||||
@ -1723,17 +1746,23 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
|||||||
victim->list_size -= sub_size;
|
victim->list_size -= sub_size;
|
||||||
victim->remaining -= sub_size;
|
victim->remaining -= sub_size;
|
||||||
}
|
}
|
||||||
progress_unlock();
|
|
||||||
|
|
||||||
target->list_size = sub_size;
|
target->list_size = sub_size;
|
||||||
target->remaining = sub_size;
|
target->remaining = sub_size;
|
||||||
pthread_mutex_unlock(&data_ready);
|
target->working = 1;
|
||||||
|
progress_unlock();
|
||||||
|
|
||||||
|
pthread_mutex_lock(&target->mutex);
|
||||||
|
target->data_ready = 1;
|
||||||
|
pthread_cond_signal(&target->cond);
|
||||||
|
pthread_mutex_unlock(&target->mutex);
|
||||||
|
|
||||||
if (!sub_size) {
|
if (!sub_size) {
|
||||||
pthread_join(target->thread, NULL);
|
pthread_join(target->thread, NULL);
|
||||||
|
pthread_cond_destroy(&target->cond);
|
||||||
|
pthread_mutex_destroy(&target->mutex);
|
||||||
active_threads--;
|
active_threads--;
|
||||||
}
|
}
|
||||||
} while (active_threads);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
Loading…
Reference in New Issue
Block a user