null

Этот коварный pthread_cond_signal

Для того, чтобы ожидать некоего события на условной переменной (Condition Variable) надо захватывать сопутствующую ей мьютекс-блокировку. На время ожидания поток освобождает ее, но как только внешний поток пошлет ей сигнал, она снова захватит ее. Сделано это для того, чтобы поток-производитель и поток-потребитель не конфликтовали за общий ресурс (например, очередь, в которую производитель кладет сообщение):

Код потребителя

void* squeue_pop(squeue_t* sq) {
	/* Объявления */    

	pthread_mutex_lock(&sq->sq_mutex);

retry:
	if(sq->sq_head == NULL) {
		pthread_cond_wait(&sq->sq_cv, &sq->sq_mutex);

		if(!sq->sq_is_destroyed)
			goto retry;
		else
			return NULL;
	}

        /* Извлекаем элемент */

	pthread_mutex_unlock(&sq->sq_mutex);

	/* Возвращаем объект */
}

 

Код производителя:

void squeue_push(squeue_t* sq, void* object) {
        /* Объявления */        

	pthread_mutex_lock(&sq->sq_mutex);

	/* Помещаем элемент в очередь */
	
        pthread_cond_signal(&sq->sq_cv);
	pthread_mutex_unlock(&sq->sq_mutex);
}

 

В идеале данный код должен работать следующим образом:


ПОТРЕБИТЕЛЬ                 | ПРОИЗВОДИТЕЛЬ
pthread_mutex_lock          |
                            |
pthread_cond_wait           |              
    pthread_mutex_unlock    |
    [WAITING]               |
                            | ...
                            | pthread_mutex_lock
                            | [QUEUE msg]
                            | pthread_cond_signal
                            | pthread_mutex_unlock
                            |
    pthread_mutex_lock      |
                            |
[PROCESS msg]               |


А вот всегда ли нужно захватывать блокировку на стороне производителя? man-страница на pthread_cond_signal с этим не очень-то помогает:

       The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not
       it  currently  owns  the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have
       associated with the condition variable during their waits; however, if predictable  scheduling  behavior
       is  required,  then  that  mutex  shall  be  locked  by  the  thread calling pthread_cond_broadcast() or
       pthread_cond_signal().


Таким образом, мьютекс может удерживаться, а может и нет, а для предсказуемого поведения планировщика рекомендуется все же захватывать его. Некоторую подсказку на этот счет дает stackoverflow: http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex

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

ПОТРЕБИТЕЛЬ                 | ПРОИЗВОДИТЕЛЬ
pthread_mutex_lock          |
                            | pthread_cond_signal
pthread_cond_wait           |              
    pthread_mutex_unlock    |
    [WAITING]               |
                            |


Однако если же мы используем внешнее условие - например проверяя голову очереди на равенство NULL, то удерживание мьютекса на время вызова pthread_cond_signal приведет к излишним переключениям контекста:

ПОТРЕБИТЕЛЬ                 | ПРОИЗВОДИТЕЛЬ         ПРОИЗВ | ПОТР   МЬЮТЕКС
pthread_mutex_lock          |                         |             ПОТР
                            |                         |             ПОТР
pthread_cond_wait           |                         |             ПОТР      
    pthread_mutex_unlock    |                         |             ПОТР
    [WAITING]               |                         +-------+      -
                            | ...                             |      -
                            | pthread_mutex_lock              |     ПРОИЗВ
                            | [QUEUE msg]                     |     ПРОИЗВ  
                            | pthread_cond_signal     +-------+     ПРОИЗВ
                            |                         |             ПРОИЗВ
    pthread_mutex_lock      |                         +-------+     ПРОИЗВ  
                            |                                 |     ПРОИЗВ  
                            | pthread_mutex_unlock    +-------+     ПРОИЗВ
                            |                         |             ПОТР  
[PROCESS msg]               |                         |             ПОТР


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

Решением этой проблемы является уже упомянутое "внешнее условие" и освобождение мьютекса до вызова pthread_cond_signal:

ПОТРЕБИТЕЛЬ                 | ПРОИЗВОДИТЕЛЬ         ПРОИЗВ | ПОТР   МЬЮТЕКС
pthread_mutex_lock          |                         |             ПОТР        
                            |                         |             ПОТР         
pthread_cond_wait           |                         |             ПОТР          
    pthread_mutex_unlock    |                         |             ПОТР          
    [WAITING]               |                         +-------+      -  
                            | ...                             |      -
                            | pthread_mutex_lock              |     ПРОИЗВ   
                            | [QUEUE msg]                     |     ПРОИЗВ  
                            | pthread_mutex_unlock            |     ПРОИЗВ
                            | ...                             |      -
                            | pthread_cond_signal     +-------+      -                    
    pthread_mutex_lock      |                         |             ПОТР
                            |                         |             ПОТР         
[PROCESS msg]               |                         |             ПОТР

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs