Являются ли исключения С++ достаточными для реализации локального хранилища потоков?
Я был комментируя ответ, что поточно-локальное хранилище приятно и напомнило о другом информативном обсуждении о исключения, в которых я предположил
Единственная особенность в среда выполнения внутри броска block является то, что объект исключения на которые ссылается ретрон.
Объединяя два и два вместе, не выполнил бы весь поток внутри блока функций-catch своей основной функции, наложил бы его на локальное хранилище потоков?
Кажется, он работает нормально, хотя и медленно. Этот роман или хорошо охарактеризован? Есть ли другой способ решения проблемы? Правильно ли была моя первоначальная предпосылка? Какие накладные расходы 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()
, но в целом это не похоже на непроизводительное утро, случившееся в бессоннице...
EDIT: Похоже, что GCC выполняет три вызова pthread_getspecific
в get_thread
. EDIT: и много неприятной интроспекции в стек, среду и исполняемый формат, чтобы найти блок catch
, который я пропустил в первом прохождении. Это выглядит очень зависимым от платформы, поскольку GCC вызывает некоторые libunwind
из ОС. Накладные расходы порядка 4000 циклов. Я полагаю, что он также должен пройти иерархию классов, но это может быть под контролем.
Ответы
Ответ 1
В игривом духе вопроса я предлагаю это ужасное создание кошмара:
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;
}
};
Воспользовавшись тем, что в соответствии со стандартом С++ strtok
ставит свой первый аргумент, чтобы последующие вызовы могли пройти 0
, чтобы извлечь дальнейшие токены из одной и той же строки, поэтому в реализации, зависящей от потока, она должна использовать TLS.
example *e = new example;
tls::singleton().set(e);
example *e2 = reinterpret_cast<example *>(tls::singleton().get());
Итак, пока strtok
не используется по назначению в другом месте программы, у нас есть еще один запасной слот TLS.
Ответ 2
Я думаю, вы на что-то здесь. Это может быть даже переносимым способом получения данных в обратные вызовы, которые не принимают пользовательскую переменную "состояние", как вы уже упоминали, даже вне явного использования потоков.
Итак, похоже, что вы ответили на вопрос в теме: ДА.
Ответ 3
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 создает актуальные вызовы локального хранилища потоков. Это действительно делает проблему интересной. Я все еще не совсем уверен, является ли это примером для других компиляторов и как он отличается от прямого обращения к хранилищу потоков.
Я по-прежнему придерживаюсь своего общего аргумента, что одни и те же данные могут быть доступны более простым и прямым способом, будь то аргументы, стекирование ходов или локальное хранилище потоков.
Ответ 4
Доступ к данным в текущем стеке вызовов функций всегда является потокобезопасным. Именно поэтому ваш код является потокобезопасным, а не из-за умного использования исключений. Локальное хранилище потоков позволяет нам хранить данные по потокам и ссылаться на них за пределами быстрого стека вызовов.