@matheusbsbs / matheus-bagliano / commits / 50f22ada52
Fork 0

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>

Johannes Sixt committed Dec 16, 2007 at 20:45 UTC 50f22ada523f448986dd6273da14a477cea5a646
1 file changed +79 -50
builtin-pack-objects.c
+79 -50
@@ -1594,6 +1594,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
1594
1595 #ifdef THREADED_DELTA_SEARCH
1596
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 +
1606 struct thread_params {
1607 pthread_t thread;
1608 struct object_entry **list;
@@ -1601,37 +1610,50 @@ struct thread_params {
1610 unsigned remaining;
1611 int window;
1612 int depth;
1613 + int working;
1614 + int data_ready;
1615 + pthread_mutex_t mutex;
1616 + pthread_cond_t cond;
1617 unsigned *processed;
1618 };
1619
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;
1621
1622 static void *threaded_find_deltas(void *arg)
1623 {
1624 struct thread_params *me = arg;
1625
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) {
1627 find_deltas(me->list, &me->remaining,
1628 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);
1648 }
1649 + /* leave ->working 1 so that this doesn't get more work assigned */
1650 + return NULL;
1651 }
1652
1653 static void ll_find_deltas(struct object_entry **list, unsigned list_size,
1654 int window, int depth, unsigned *processed)
1655 {
1634 - struct thread_params *target, p[delta_search_threads];
1656 + struct thread_params p[delta_search_threads];
1657 int i, ret, active_threads = 0;
1658
1659 if (delta_search_threads <= 1) {
@@ -1639,49 +1661,42 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
1661 return;
1662 }
1663
1642 - pthread_mutex_lock(&data_provider);
1643 - pthread_mutex_lock(&data_ready);
1644 -
1645 - /* Start work threads. */
1664 + /* Partition the work amongst work threads. */
1665 for (i = 0; i < delta_search_threads; i++) {
1666 + unsigned sub_size = list_size / (delta_search_threads - i);
1667 +
1668 p[i].window = window;
1669 p[i].depth = depth;
1670 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);
1675
1676 /* try to split chunks on "path" boundaries */
1677 while (sub_size < list_size && list[sub_size]->hash &&
1678 list[sub_size]->hash == list[sub_size-1]->hash)
1679 sub_size++;
1680
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;
1684
1685 list += sub_size;
1686 list_size -= sub_size;
1687 }
1688
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 +
1700 /*
1701 * Now let's wait for work completion. Each time a thread is done
1702 * 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,
1705 * until the remaining object list segments are simply too short
1706 * to be worth splitting anymore.
1707 */
1693 - do {
1708 + while (active_threads) {
1709 + struct thread_params *target = NULL;
1710 struct thread_params *victim = NULL;
1711 unsigned sub_size = 0;
1696 - pthread_mutex_lock(&data_provider);
1697 - target = data_requester;
1712
1713 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 +
1723 for (i = 0; i < delta_search_threads; i++)
1724 if (p[i].remaining > 2*window &&
1725 (!victim || victim->remaining < p[i].remaining))
@@ -1723,17 +1746,23 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
1746 victim->list_size -= sub_size;
1747 victim->remaining -= sub_size;
1748 }
1726 - progress_unlock();
1727 -
1749 target->list_size = sub_size;
1750 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);
1758
1759 if (!sub_size) {
1760 pthread_join(target->thread, NULL);
1761 + pthread_cond_destroy(&target->cond);
1762 + pthread_mutex_destroy(&target->mutex);
1763 active_threads--;
1764 }
1736 - } while (active_threads);
1765 + }
1766 }
1767
1768 #else