Skip to content

Commit 50f22ad

Browse files
Johannes Sixtgitster
authored andcommitted
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>
1 parent 3eb2a15 commit 50f22ad

File tree

1 file changed

+79
-50
lines changed

1 file changed

+79
-50
lines changed

builtin-pack-objects.c

Lines changed: 79 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,94 +1594,109 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
15941594

15951595
#ifdef THREADED_DELTA_SEARCH
15961596

1597+
/*
1598+
* The main thread waits on the condition that (at least) one of the workers
1599+
* has stopped working (which is indicated in the .working member of
1600+
* struct thread_params).
1601+
* When a work thread has completed its work, it sets .working to 0 and
1602+
* signals the main thread and waits on the condition that .data_ready
1603+
* becomes 1.
1604+
*/
1605+
15971606
struct thread_params {
15981607
pthread_t thread;
15991608
struct object_entry **list;
16001609
unsigned list_size;
16011610
unsigned remaining;
16021611
int window;
16031612
int depth;
1613+
int working;
1614+
int data_ready;
1615+
pthread_mutex_t mutex;
1616+
pthread_cond_t cond;
16041617
unsigned *processed;
16051618
};
16061619

1607-
static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER;
1608-
static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER;
1609-
static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
1610-
static struct thread_params *data_requester;
1620+
static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER;
16111621

16121622
static void *threaded_find_deltas(void *arg)
16131623
{
16141624
struct thread_params *me = arg;
16151625

1616-
for (;;) {
1617-
pthread_mutex_lock(&data_request);
1618-
data_requester = me;
1619-
pthread_mutex_unlock(&data_provider);
1620-
pthread_mutex_lock(&data_ready);
1621-
pthread_mutex_unlock(&data_request);
1622-
1623-
if (!me->remaining)
1624-
return NULL;
1625-
1626+
while (me->remaining) {
16261627
find_deltas(me->list, &me->remaining,
16271628
me->window, me->depth, me->processed);
1629+
1630+
progress_lock();
1631+
me->working = 0;
1632+
pthread_cond_signal(&progress_cond);
1633+
progress_unlock();
1634+
1635+
/*
1636+
* We must not set ->data_ready before we wait on the
1637+
* condition because the main thread may have set it to 1
1638+
* before we get here. In order to be sure that new
1639+
* work is available if we see 1 in ->data_ready, it
1640+
* was initialized to 0 before this thread was spawned
1641+
* and we reset it to 0 right away.
1642+
*/
1643+
pthread_mutex_lock(&me->mutex);
1644+
while (!me->data_ready)
1645+
pthread_cond_wait(&me->cond, &me->mutex);
1646+
me->data_ready = 0;
1647+
pthread_mutex_unlock(&me->mutex);
16281648
}
1649+
/* leave ->working 1 so that this doesn't get more work assigned */
1650+
return NULL;
16291651
}
16301652

16311653
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
16321654
int window, int depth, unsigned *processed)
16331655
{
1634-
struct thread_params *target, p[delta_search_threads];
1656+
struct thread_params p[delta_search_threads];
16351657
int i, ret, active_threads = 0;
16361658

16371659
if (delta_search_threads <= 1) {
16381660
find_deltas(list, &list_size, window, depth, processed);
16391661
return;
16401662
}
16411663

1642-
pthread_mutex_lock(&data_provider);
1643-
pthread_mutex_lock(&data_ready);
1644-
1645-
/* Start work threads. */
1664+
/* Partition the work amongst work threads. */
16461665
for (i = 0; i < delta_search_threads; i++) {
1666+
unsigned sub_size = list_size / (delta_search_threads - i);
1667+
16471668
p[i].window = window;
16481669
p[i].depth = depth;
16491670
p[i].processed = processed;
1650-
p[i].remaining = 0;
1651-
ret = pthread_create(&p[i].thread, NULL,
1652-
threaded_find_deltas, &p[i]);
1653-
if (ret)
1654-
die("unable to create thread: %s", strerror(ret));
1655-
active_threads++;
1656-
}
1657-
1658-
/* Then partition the work amongst them. */
1659-
for (i = 0; i < delta_search_threads; i++) {
1660-
unsigned sub_size = list_size / (delta_search_threads - i);
1661-
1662-
pthread_mutex_lock(&data_provider);
1663-
target = data_requester;
1664-
if (!sub_size) {
1665-
pthread_mutex_unlock(&data_ready);
1666-
pthread_join(target->thread, NULL);
1667-
active_threads--;
1668-
continue;
1669-
}
1671+
p[i].working = 1;
1672+
p[i].data_ready = 0;
1673+
pthread_mutex_init(&p[i].mutex, NULL);
1674+
pthread_cond_init(&p[i].cond, NULL);
16701675

16711676
/* try to split chunks on "path" boundaries */
16721677
while (sub_size < list_size && list[sub_size]->hash &&
16731678
list[sub_size]->hash == list[sub_size-1]->hash)
16741679
sub_size++;
16751680

1676-
target->list = list;
1677-
target->list_size = sub_size;
1678-
target->remaining = sub_size;
1679-
pthread_mutex_unlock(&data_ready);
1681+
p[i].list = list;
1682+
p[i].list_size = sub_size;
1683+
p[i].remaining = sub_size;
16801684

16811685
list += sub_size;
16821686
list_size -= sub_size;
16831687
}
16841688

1689+
/* Start work threads. */
1690+
for (i = 0; i < delta_search_threads; i++) {
1691+
if (!p[i].list_size)
1692+
continue;
1693+
ret = pthread_create(&p[i].thread, NULL,
1694+
threaded_find_deltas, &p[i]);
1695+
if (ret)
1696+
die("unable to create thread: %s", strerror(ret));
1697+
active_threads++;
1698+
}
1699+
16851700
/*
16861701
* Now let's wait for work completion. Each time a thread is done
16871702
* 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,
16901705
* until the remaining object list segments are simply too short
16911706
* to be worth splitting anymore.
16921707
*/
1693-
do {
1708+
while (active_threads) {
1709+
struct thread_params *target = NULL;
16941710
struct thread_params *victim = NULL;
16951711
unsigned sub_size = 0;
1696-
pthread_mutex_lock(&data_provider);
1697-
target = data_requester;
16981712

16991713
progress_lock();
1714+
for (;;) {
1715+
for (i = 0; !target && i < delta_search_threads; i++)
1716+
if (!p[i].working)
1717+
target = &p[i];
1718+
if (target)
1719+
break;
1720+
pthread_cond_wait(&progress_cond, &progress_mutex);
1721+
}
1722+
17001723
for (i = 0; i < delta_search_threads; i++)
17011724
if (p[i].remaining > 2*window &&
17021725
(!victim || victim->remaining < p[i].remaining))
@@ -1723,17 +1746,23 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
17231746
victim->list_size -= sub_size;
17241747
victim->remaining -= sub_size;
17251748
}
1726-
progress_unlock();
1727-
17281749
target->list_size = sub_size;
17291750
target->remaining = sub_size;
1730-
pthread_mutex_unlock(&data_ready);
1751+
target->working = 1;
1752+
progress_unlock();
1753+
1754+
pthread_mutex_lock(&target->mutex);
1755+
target->data_ready = 1;
1756+
pthread_cond_signal(&target->cond);
1757+
pthread_mutex_unlock(&target->mutex);
17311758

17321759
if (!sub_size) {
17331760
pthread_join(target->thread, NULL);
1761+
pthread_cond_destroy(&target->cond);
1762+
pthread_mutex_destroy(&target->mutex);
17341763
active_threads--;
17351764
}
1736-
} while (active_threads);
1765+
}
17371766
}
17381767

17391768
#else

0 commit comments

Comments
 (0)