Достаточно ли исключений C ++ для реализации локального потока? - PullRequest
26 голосов
/ 21 марта 2010

Я комментировал ответ , что локальное хранилище хорошо, и вспомнил другое информативное обсуждение об исключениях, где я предполагал

Единственная особенность среда выполнения в броске блок в том, что объект исключения ссылается на rethrow.

Если сложить два и два вместе, разве не будет выполнен весь поток внутри блока функции-перехвата его основной функции, наполнить его локальным хранилищем потока?

Кажется, все работает нормально, хотя и медленно. Это роман или хорошо охарактеризован? Есть ли другой способ решения проблемы? Была ли моя первоначальная предпосылка правильной? Какие издержки get_thread возникают на вашей платформе? Каков потенциал для оптимизации?

#include <iostream>
#include <pthread.h>
using namespace std;

struct thlocal {
    string name;
    thlocal( string const &n ) : name(n) {}
};

struct thread_exception_base {
    thlocal &th;
    thread_exception_base( thlocal &in_th ) : th( in_th ) {}
    thread_exception_base( thread_exception_base const &in ) : th( in.th ) {}
};

thlocal &get_thread() throw() {
    try {
        throw;
    } catch( thread_exception_base &local ) {
        return local.th;
    }
}

void print_thread() {
    cerr << get_thread().name << endl;
}

void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw thread_exception_base( local );
} catch( thread_exception_base & ) {
    print_thread();

    return NULL;
}

int main() {
    thlocal local( "main" );
    try {
        throw thread_exception_base( local );
    } catch( thread_exception_base & ) {
        print_thread();

        pthread_t th;
        thlocal kid_local( "kid" );
        pthread_create( &th, NULL, &kid, &kid_local );
        pthread_join( th, NULL );

        print_thread();
    }

    return 0;
}

Это требует определения новых классов исключений, полученных из thread_exception_base, инициализации базы с помощью get_thread(), но в целом это не похоже на непродуктивную бессонницу в воскресенье утром ...

РЕДАКТИРОВАТЬ: Похоже, GCC делает три звонки на pthread_getspecific в get_thread. РЕДАКТИРОВАТЬ: и множество неприятных самоанализа в стеке, среде и исполняемом формате, чтобы найти блок catch, который я пропустил при первом прохождении. Это выглядит сильно зависящим от платформы, так как GCC вызывает около 1030 * из ОС. Накладные расходы порядка 4000 циклов. Я предполагаю, что это также должно пересечь иерархию классов, но это можно держать под контролем.

Ответы [ 4 ]

9 голосов
/ 02 июля 2010

В игривом духе вопроса, я предлагаю это ужасное создание кошмара:

class tls
{
    void push(void *ptr)
    {
        // allocate a string to store the hex ptr 
        // and the hex of its own address
        char *str = new char[100];
        sprintf(str, " |%x|%x", ptr, str);
        strtok(str, "|");
    }

    template <class Ptr>
    Ptr *next()
    {
        // retrieve the next pointer token
        return reinterpret_cast<Ptr *>(strtoul(strtok(0, "|"), 0, 16));
    }

    void *pop()
    {
        // retrieve (and forget) a previously stored pointer
        void *ptr = next<void>();
        delete[] next<char>();
        return ptr;
    }

    // private constructor/destructor
    tls() { push(0); }
    ~tls() { pop(); }

public:
    static tls &singleton()
    {
        static tls i;
        return i;
    }

    void *set(void *ptr)
    {
        void *old = pop();
        push(ptr);
        return old;
    }

    void *get()
    {
        // forget and restore on each access
        void *ptr = pop();
        push(ptr);
        return ptr;
    }
};

Используя тот факт, что в соответствии со стандартом C ++, strtok сохраняет свой первый аргумент, чтобы последующие вызовы могли передавать 0, чтобы получить дополнительные токены из той же строки, поэтому в реализации с поддержкой потоков он должен использовать TLS.

example *e = new example;

tls::singleton().set(e);

example *e2 = reinterpret_cast<example *>(tls::singleton().get());

Так что, пока strtok не используется по назначению где-либо еще в программе, у нас есть еще один свободный слот TLS.

3 голосов
/ 21 марта 2010

Я думаю, что вы на что-то здесь.Это может быть даже переносимый способ передачи данных в обратные вызовы, которые не принимают пользовательскую переменную «состояния», как вы упомянули, даже если вы не используете явное использование потоков.

Так что это звучит как выВы ответили на вопрос в вашей теме: ДА.

0 голосов
/ 21 марта 2010

Доступ к данным в текущем стеке вызовов функций всегда безопасен для потоков.Вот почему ваш код является потокобезопасным, а не из-за умного использования исключений.Локальное хранилище потоков позволяет нам хранить данные для каждого потока и ссылаться на них вне стека немедленных вызовов.

0 голосов
/ 21 марта 2010
void *kid( void *local_v ) try {
    thlocal &local = * static_cast< thlocal * >( local_v );
    throw local;
} catch( thlocal & ) {
    print_thread();

    return NULL;
}

==

void *kid (void *local_v ) { print_thread(local_v); }

Возможно, я что-то здесь упускаю, но это не локальное хранилище потока, а просто излишне сложная передача аргументов.Аргумент различен для каждого потока только потому, что он передается в pthread_create, а не из-за каких-либо манипуляций с исключениями.


Оказалось, что мне действительно не хватало того, что в этом примере GCC производит реальные вызовы локального хранилища потока,Это на самом деле делает проблему интересной.Я до сих пор не совсем уверен, так ли это для других компиляторов, и чем он отличается от непосредственного вызова хранилища потоков.

Я все еще придерживаюсь своего общего аргумента, что к тем же данным можно получить доступ в болеепростой и понятный способ, будь то аргументы, обход стека или локальное хранилище потоков.

...