2012-02-02 3 views
15

Раньше я думал, что все реентеративные функции являются потокобезопасными. Но я читал Reentrancy page in Wiki, он выполняет код, который «совершенно возвратный, но не поточно-., Потому что это не обеспечивает глобальные данные в целостном состоянии во время исполнения»Почему этот код реентерабелен, но не потокобезопасен

int t; 

void swap(int *x, int *y) 
{ 
     int s; 

     s = t; // save global variable 
     t = *x; 
     *x = *y; 
     // hardware interrupt might invoke isr() here! 
     *y = t; 
     t = s; // restore global variable 
} 

void isr() 
{ 
     int x = 1, y = 2; 
     swap(&x, &y); 
} 

Я не понимаю его объяснение , Почему эта функция не является потокобезопасной? Это потому, что глобальная переменная int t будет изменена при выполнении потоков?

+0

Этот пример более чем надуман. Но, повторное проникновение и потокобезопасность являются ортогональными понятиями. –

+1

У Posix есть еще одно определение для повторного размещения «В POSIX.1c« реентеративная функция »определяется как« функция, эффект которой при вызове двух или более потоков гарантированно будет таким, как если бы потоки каждый выполняли функцию один за другим в неопределенном порядке, даже если фактическое исполнение чередуется », что пример (очень плохой) в википедии не соответствовал бы – nos

+0

Мне кажется, что этот пример не является реентерабельным: прерванный' swap() 'будет не меняйте значения, обозначенные символами 'x' и' y', как ожидалось ('* y' может быть установлено равным 2 независимо от начального значения' * x'. – rom1v

ответ

4

Чтобы дать более общий ответ, повторный вход только на уровне функции. Это означает, что один вызов функции не изменяет состояние, на котором может измениться функционирование второго вызова.

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

Пример Беспоставочной функции возвратной является strtok

Это, например, не представляется возможным гнездо 2 разбора петля с ним:

/* To read a several lines of comma separated numbers */ 
char buff[WHATEVER], *p1, *p2; 

    p1 = strtok(buff, "\n"); 
    while(p1) { 
    p2 = strtok(p1, ","); 
    while(p2) { 
     atoi(p2); 
     p2 = strtok(NULL, ","); 
     } 
    } 
    p1 = strtok(NULL, "\n"); 
    } 

Это не работает, потому что состояние внешнего Строка strtok сбивается вторым вызовом (нужно использовать вариант реентера strtok_r).

+3

Вопрос задавал вопрос о том, как могут быть возвратные функции небезопасный поток.Это не отвечает на вопрос, он просто дает пример обратного. – Jed

0

Таким образом, функция messes с глобальной переменной t называется некоторой причудливой причиной. Если эта функция вызывается из двух разных потоков одновременно, возможно, что вы получите неожиданные и неправильные результаты, потому что один экземпляр перезапишет значение в t, которое было написано другим экземпляром.

1

Если у вас было 2 экземпляра (каждый в другом потоке), выполняющих его, можно было бы наступить на носки друг друга: если кто-то прервал комментарий «аппаратное прерывание», а другой выполнил, он мог бы изменить t, так что возврат к первому приведет к неправильным результатам.

4

Трюк с этим типом повторения заключается в том, что выполнение первого вызова прекращается во время второго вызова. Также как вызов подфункции. Первый вызов продолжается после завершения второго вызова. Поскольку функция сохраняет состояние t при входе и восстанавливает его при выходе, ничто не изменилось для первого вызова, когда оно продолжается. Поэтому у вас всегда есть определенный и строгий порядок исполнения, независимо от того, где именно первый вызов прерывается.

Когда эта функция работает в нескольких потоках, все исполнения выполняются параллельно, даже в истинной параллели с многоядерным процессором. Нет определенного порядка выполнения по всем потокам, только в пределах одного потока. Таким образом, значение t может быть изменено в любой момент одним из других потоков.

+0

Таким образом, повторное включение не включает параллель? Например, foo() {a(); b()}, когда происходит повторное включение , это может быть только (ab) b, но может " t a a b b, который может быть многопоточным? –

+0

В принципе да. Резерент, как указано здесь, всегда (a2 b2), потому что внутренний вызов всегда выполняется полностью до того, как наружный вызов продолжается. В многопоточности это зависит от графика потока и может быть также a1 a2 b1 b2. Создание функции реентерабера не означает, что она является потокобезопасной. И наоборот: сделать его поточно-безопасным не означает, что он является реентерабельным. Оба должны обрабатываться отдельно, всегда возникает вопрос, может ли и как состояние (т. Е. Переменные) делиться между вызовами и между потоками. – Secure

+0

Просто для заметки. Для меня было очень трудно представить себе «повторную запись только из той же темы» *. Поскольку я боюсь heisenbug, я тренировался, чтобы подразумевать параллельное выполнение потоков. И я видел термин * reentrancy * только из потока, связанного с текстом. Я думаю, что это то же самое для людей, которые начали программировать после того, как превентивная многопоточность стала обычной. Но по иронии судьбы, это ключевой момент, чтобы понять этот термин. – Eonil

2

Я собираюсь предложить другой (возможно, менее надуманный) пример функции, которая является реентерабельной, но не потокобезопасной.

Вот реализация «Башня Ханоя», используя общий глобальный «Темп» стек:

stack_t tmp; 

void hanoi_inner(stack_t src, stack_t dest, stack_t tmp, int n) 
{ 
    if (n == 1) move(src, dest) 
    else { 
    hanoi_inner(src, tmp, dest, n - 1); 
    move(src, dest); 
    hanoi_inner(tmp, dest, src, n - 1); 
    } 
} 

void hanoi(stack_t src, stack_t dest, int n) { hanoi_inner(src, dest, tmp, n); } 

Функция hanoi() реентерабельна, поскольку она покидает состояние глобального буфера tmp неизменной при его возвращает (одно предостережение: обычное ограничение на увеличение размера дисков на tmp может быть нарушено во время повторного вызова.) Однако hanoi() не является потокобезопасным.

Вот пример, который является одновременно потокобезопасным и возвратным, если оператор инкремента n++ атомичен:

int buf[MAX_SIZE]; /* global, shared buffer structure */ 
int n;    /* global, shared counter */ 

int* alloc_int() { return &buf[n++]; } 

Вы действительно мог бы использовать это в качестве распределителя для одного целых клеток (не проверяет переполнение, я знаю). Если n++ не является атомной операцией, два потока или два реентерабельных вызова могут в конечном итоге быть выделены одной и той же ячейкой.

+1

Этот только реентерабельный, если n ++ является атомарным (которого обычно нет). В противном случае оба вызова могли бы вернуть один и тот же указатель. –

+0

@per Спасибо - я попытаюсь изменить его как реентерабельный, не прибегая к блокировке или атомному n ++ – gcbenison

3

Предположим, резьба А и В. Автор Тема А имеет две локальные переменные а = 5, б = 10 и резьба В имеет две локальные переменные р = 20, Q = 30.

Тема: А вызывает своп (& a, & b); (& p, & q);

Я предполагаю, что оба потока работают на разных ядрах и принадлежат к одному процессу. Переменная t является глобальной и int x, int y являются локальными для данной функции. Следующее планирование потоков показывает, как значение «t» может меняться в зависимости от планирования потоков и, следовательно, небезопасно. Скажем, глобальный t = 100;

Thread A   Thread B 
1) int s;  int s; 
2) s = 100;  s = 100; 
3) t = 5;  no operation(nop); 
4) nop;   t = 20; // t is global so Thread A also sees the value as t = 20 
5) x = 10;  x = 30; 
6) y = 20;  y = 20; // Thread A exchange is wrong, Thread B exchange is OK 

Теперь попробуйте представить, что бы произошло, если утверждения 3 и 4 находятся в другом порядке выше. t будет получать значение 5 и обмен в потоке B будет неправильным. Ситуация еще проще, если два потока находятся на одном процессоре. Тогда ни одна из вышеперечисленных операций не будет одновременной. Я только что показал чередование на шагах 3 и 4, поскольку они являются наиболее важными.