1.Linux“線程”
進程與線程之間是有區別的,不過linux內核只提供了輕量進程的支持,未實現線程模型。Linux是一種“多進程單線程”的操作系統。Linux本身只有進程的概念,而其所謂的“線程”本質上在內核里仍然是進程。
大家知道,進程是資源分配的單位,同一進程中的多個線程共享該進程的資源(如作為共享內存的全局變量)。Linux中所謂的“線程”只是在被創建時clone了父進程的資源,因此clone出來的進程表現為“線程”,這一點一定要弄清楚。因此,Linux“線程”這個概念只有在打冒號的情況下才是最准確的。
目前Linux中最流行的線程機制為LinuxThreads,所采用的就是線程-進程“一對一”模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)負責開發完成,並已綁定在GLIBC中發行,它實現了一種BiCapitalized面向Linux的Posix 1003.1c “pthread”標准接口。Linuxthread可以支持Intel、Alpha、MIPS等平台上的多處理器系統。
按照POSIX 1003.1c 標准編寫的程序與Linuxthread 庫相鏈接即可支持Linux平台上的多線程,在程序中需包含頭文件pthread. h,在編譯鏈接時使用命令:
gcc -D -REENTRANT -lpthread xxx. c
其中-REENTRANT宏使得相關庫函數(如stdio.h、errno.h中函數) 是可重入的、線程安全的(thread-safe),-lpthread則意味着鏈接庫目錄下的libpthread.a或libpthread.so文件。使用Linuxthread庫需要2.0以上版本的Linux內核及相應版本的C庫(libc 5.2.18、libc 5.4.12、libc 6)。
2.“線程”控制
線程創建
進程被創建時,系統會為其創建一個主線程,而要在進程中創建新的線程,則可以調用pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(start_routine)(void*), void *arg);
start_routine為新線程的入口函數,arg為傳遞給start_routine的參數。
每個線程都有自己的線程ID,以便在進程內區分。線程ID在pthread_create調用時回返給創建線程的調用者;一個線程也可以在創建后使用pthread_self()調用獲取自己的線程ID:
pthread_self (void) ;
線程退出
線程的退出方式有三:
(1)執行完成后隱式退出;
(2)由線程本身顯示調用pthread_exit 函數退出;
pthread_exit (void * retval) ;
(3)被其他線程用pthread_cance函數終止:
pthread_cance (pthread_t thread) ;
在某線程中調用此函數,可以終止由參數thread 指定的線程。
如果一個線程要等待另一個線程的終止,可以使用pthread_join函數,該函數的作用是調用pthread_join的線程將被掛起直到線程ID為參數thread的線程終止:
pthread_join (pthread_t thread, void** threadreturn);
3.線程通信
線程互斥
互斥意味着“排它”,即兩個線程不能同時進入被互斥保護的代碼。Linux下可以通過pthread_mutex_t 定義互斥體機制完成多線程的互斥操作,該機制的作用是對某個需要互斥的部分,在進入時先得到互斥體,如果沒有得到互斥體,表明互斥部分被其它線程擁有,此時欲獲取互斥體的線程阻塞,直到擁有該互斥體的線程完成互斥部分的操作為止。
下面的代碼實現了對共享全局變量x 用互斥體mutex 進行保護的目的:
int x; // 進程中的全局變量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的屬性初始化互斥體變量mutex
pthread_mutex_lock(&mutex); // 給互斥體變量加鎖
… //對變量x 的操作
phtread_mutex_unlock(&mutex); // 給互斥體變量解除鎖
線程同步
同步就是線程等待某個事件的發生。只有當等待的事件發生線程才繼續執行,否則線程掛起並放棄處理器。當多個線程協作時,相互作用的任務必須在一定的條件下同步。
Linux下的C語言編程有多種線程同步機制,最典型的是條件變量(condition variable)。pthread_cond_init用來創建一個條件變量,其函數原型為:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
pthread_cond_wait和pthread_cond_timedwait用來等待條件變量被設置,值得注意的是這兩個等待調用需要一個已經上鎖的互斥體mutex,這是為了防止在真正進入等待狀態之前別的線程有可能設置該條件變量而產生競爭。pthread_cond_wait的函數原型為:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_broadcast用於設置條件變量,即使得事件發生,這樣等待該事件的線程將不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ;
pthread_cond_signal則用於解除某一個等待線程的阻塞狀態:
pthread_cond_signal (pthread_cond_t *cond) ;
pthread_cond_destroy 則用於釋放一個條件變量的資源。
在頭文件semaphore.h 中定義的信號量則完成了互斥體和條件變量的封裝,按照多線程程序設計中訪問控制機制,控制對資源的同步訪問,提供程序設計人員更方便的調用接口。
sem_init(sem_t *sem, int pshared, unsigned int val);
這個函數初始化一個信號量sem 的值為val,參數pshared 是共享屬性控制,表明是否在進程間共享。
sem_wait(sem_t *sem);
調用該函數時,若sem為無狀態,調用線程阻塞,等待信號量sem值增加(post )成為有信號狀態;若sem為有狀態,調用線程順序執行,但信號量的值減一。
sem_post(sem_t *sem);
調用該函數,信號量sem的值增加,可以從無信號狀態變為有信號狀態。
4.實例
下面我們還是以名的生產者/消費者問題為例來闡述Linux線程的控制和通信。一組生產者線程與一組消費者線程通過緩沖區發生聯系。生產者線程將生產的產品送入緩沖區,消費者線程則從中取出產品。緩沖區有N 個,是一個環形的緩沖池。
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 16 // 緩沖區數量
struct prodcons
{
// 緩沖區相關數據結構
int buffer[BUFFER_SIZE]; /* 實際數據存放的數組*/
pthread_mutex_t lock; /* 互斥體lock 用於對緩沖區的互斥操作 */
int readpos, writepos; /* 讀寫指針*/
pthread_cond_t notempty; /* 緩沖區非空的條件變量 */
pthread_cond_t notfull; /* 緩沖區未滿的條件變量 */
};
/* 初始化緩沖區結構 */
void init(struct prodcons *b)
{
pthread_mutex_init(&b->lock, NULL);
pthread_cond_init(&b->notempty, NULL);
pthread_cond_init(&b->notfull, NULL);
b->readpos = 0;
b->writepos = 0;
}
/* 將產品放入緩沖區,這里是存入一個整數*/
void put(struct prodcons *b, int data)
{
pthread_mutex_lock(&b->lock);
/* 等待緩沖區未滿*/
if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
{
pthread_cond_wait(&b->notfull, &b->lock);
}
/* 寫數據,並移動指針 */
b->buffer[b->writepos] = data;
b->writepos++;
if (b->writepos >= BUFFER_SIZE)
b->writepos = 0;
/* 設置緩沖區非空的條件變量*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/* 從緩沖區中取出整數*/
int get(struct prodcons *b)
{
int data;
pthread_mutex_lock(&b->lock);
/* 等待緩沖區非空*/
if (b->writepos == b->readpos)
{
pthread_cond_wait(&b->notempty, &b->lock);
}
/* 讀數據,移動讀指針*/
data = b->buffer[b->readpos];
b->readpos++;
if (b->readpos >= BUFFER_SIZE)
b->readpos = 0;
/* 設置緩沖區未滿的條件變量*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
/* 測試:生產者線程將1 到10000 的整數送入緩沖區,消費者線
程從緩沖區中獲取整數,兩者都打印信息*/
#define OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
int n;
for (n = 0; n < 10000; n++)
{
printf("%d --->\n", n);
put(&buffer, n);
} put(&buffer, OVER);
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
d = get(&buffer);
if (d == OVER)
break;
printf("--->%d \n", d);
}
return NULL;
}
int main(void)
{
pthread_t th_a, th_b;
void *retval;
init(&buffer);
/* 創建生產者和消費者線程*/
pthread_create(&th_a, NULL, producer, 0);
pthread_create(&th_b, NULL, consumer, 0);
/* 等待兩個線程結束*/
pthread_join(th_a, &retval);
pthread_join(th_b, &retval);
return 0;
}
5.WIN32、VxWorks、Linux線程類比
目前為止,筆者已經創作了《基於嵌入式操作系統VxWorks的多任務並發程序設計》(《軟件報》2006年5~12期連載)、《深入淺出Win32多線程程序設計》(天極網技術專題)系列,我們來找出這兩個系列文章與本文的共通點。
看待技術問題要瞄准其本質,不管是Linux、VxWorks還是WIN32,其涉及到多線程的部分都是那些內容,無非就是線程控制和線程通信,它們的許多函數只是名稱不同,其實質含義是等價的,下面我們來列個三大操作系統共同點詳細表單:
6.小結
本章講述了Linux下多線程的控制及線程間通信編程方法,給出了一個生產者/消費者的實例,並將Linux的多線程與WIN32、VxWorks多線程進行了類比,總結了一般規律。鑒於多線程編程已成為開發並發應用程序的主流方法,學好本章的意義也便不言自明。
完
#include <stdio.h>
#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.\n");
}
int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}
for(i=0;i<3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return (0);
}
編譯:
gcc example1.c -lpthread -o example1
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define MAX 10
pthread_t thread[2];
pthread_mutex_t mut;
int number=0, i;
void *thread1()
{
printf ("thread1 : I'm thread 1\n");
for (i = 0; i < MAX; i++)
{
printf("thread1 : number = %d\n",number);
pthread_mutex_lock(&mut);
number++;
pthread_mutex_unlock(&mut);
sleep(2);
}
printf("thread1 :主函數在等我完成任務嗎?\n");
pthread_exit(NULL);
}
void *thread2()
{
printf("thread2 : I'm thread 2\n");
for (i = 0; i < MAX; i++)
{
printf("thread2 : number = %d\n",number);
pthread_mutex_lock(&mut);
number++;
pthread_mutex_unlock(&mut);
sleep(3);
}
printf("thread2 :主函數在等我完成任務嗎?\n");
pthread_exit(NULL);
}
void thread_create(void)
{
int temp;
memset(&thread, 0, sizeof(thread)); //comment1
//創建線程
if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
printf("線程1創建失敗!\n");
else
printf("線程1被創建\n");
if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
printf("線程2創建失敗");
else
printf("線程2被創建\n");
}
void thread_wait(void)
{
//等待線程結束
if(thread[0] !=0) { //comment4
pthread_join(thread[0],NULL);
printf("線程1已經結束\n");
}
if(thread[1] !=0) { //comment5
pthread_join(thread[1],NULL);
printf("線程2已經結束\n");
}
}
int main()
{
//用默認屬性初始化互斥鎖
pthread_mutex_init(&mut,NULL);
printf("我是主函數哦,我正在創建線程,呵呵\n");
thread_create();
printf("我是主函數哦,我正在等待線程完成任務阿,呵呵\n");
thread_wait();
return 0;
}
編譯 :
gcc -lpthread -o thread_example lp.c
轉自http://www.cnblogs.com/BiffoLee/archive/2011/11/18/2254540.html
另外關於pthred_cond_wait的實現分析為何參數要傳入mutex
轉載的關於pthread_cond_wait的文章,寫的比較詳細
pthread_cond_wait()的實現原理
深入理解pthread_cond_wait、pthread_cond_signal
下載了glibc-2.31.tar看下實現
/* Block on condition variable COND. MUTEX should be held by the
calling thread. On success, MUTEX will be held by the calling
thread. */
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
__nonnull ((1, 2));
versioned_symbol (libpthread, __pthread_cond_wait, pthread_cond_wait,
GLIBC_2_3_2);
versioned_symbol (libpthread, __pthread_cond_timedwait, pthread_cond_timedwait,
GLIBC_2_3_2);
/* See __pthread_cond_wait_common. */
int
__pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex)
{
/* clockid is unused when abstime is NULL. */
return __pthread_cond_wait_common (cond, mutex, 0, NULL);
}
/* See __pthread_cond_wait_common. */
int
__pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime)
{
/* Check parameter validity. This should also tell the compiler that
it can assume that abstime is not NULL. */
if (! valid_nanoseconds (abstime->tv_nsec))
return EINVAL;
/* Relaxed MO is suffice because clock ID bit is only modified
in condition creation. */
unsigned int flags = atomic_load_relaxed (&cond->__data.__wrefs);
clockid_t clockid = (flags & __PTHREAD_COND_CLOCK_MONOTONIC_MASK)
? CLOCK_MONOTONIC : CLOCK_REALTIME;
return __pthread_cond_wait_common (cond, mutex, clockid, abstime);
}
/* This condvar implementation guarantees that all calls to signal and
broadcast and all of the three virtually atomic parts of each call to wait
(i.e., (1) releasing the mutex and blocking, (2) unblocking, and (3) re-
acquiring the mutex) happen in some total order that is consistent with the
happens-before relations in the calling program. However, this order does
not necessarily result in additional happens-before relations being
established (which aligns well with spurious wake-ups being allowed).
All waiters acquire a certain position in a 64b waiter sequence (__wseq).
This sequence determines which waiters are allowed to consume signals.
A broadcast is equal to sending as many signals as are unblocked waiters.
When a signal arrives, it samples the current value of __wseq with a
relaxed-MO load (i.e., the position the next waiter would get). (This is
sufficient because it is consistent with happens-before; the caller can
enforce stronger ordering constraints by calling signal while holding the
mutex.) Only waiters with a position less than the __wseq value observed
by the signal are eligible to consume this signal.
This would be straight-forward to implement if waiters would just spin but
we need to let them block using futexes. Futexes give no guarantee of
waking in FIFO order, so we cannot reliably wake eligible waiters if we
just use a single futex. Also, futex words are 32b in size, but we need
to distinguish more than 1<<32 states because we need to represent the
order of wake-up (and thus which waiters are eligible to consume signals);
blocking in a futex is not atomic with a waiter determining its position in
the waiter sequence, so we need the futex word to reliably notify waiters
that they should not attempt to block anymore because they have been
already signaled in the meantime. While an ABA issue on a 32b value will
be rare, ignoring it when we are aware of it is not the right thing to do
either.
Therefore, we use a 64b counter to represent the waiter sequence (on
architectures which only support 32b atomics, we use a few bits less).
To deal with the blocking using futexes, we maintain two groups of waiters:
* Group G1 consists of waiters that are all eligible to consume signals;
incoming signals will always signal waiters in this group until all
waiters in G1 have been signaled.
* Group G2 consists of waiters that arrive when a G1 is present and still
contains waiters that have not been signaled. When all waiters in G1
are signaled and a new signal arrives, the new signal will convert G2
into the new G1 and create a new G2 for future waiters.
We cannot allocate new memory because of process-shared condvars, so we
have just two slots of groups that change their role between G1 and G2.
Each has a separate futex word, a number of signals available for
consumption, a size (number of waiters in the group that have not been
signaled), and a reference count.
The group reference count is used to maintain the number of waiters that
are using the group's futex. Before a group can change its role, the
reference count must show that no waiters are using the futex anymore; this
prevents ABA issues on the futex word.
To represent which intervals in the waiter sequence the groups cover (and
thus also which group slot contains G1 or G2), we use a 64b counter to
designate the start position of G1 (inclusive), and a single bit in the
waiter sequence counter to represent which group slot currently contains
G2. This allows us to switch group roles atomically wrt. waiters obtaining
a position in the waiter sequence. The G1 start position allows waiters to
figure out whether they are in a group that has already been completely
signaled (i.e., if the current G1 starts at a later position that the
waiter's position). Waiters cannot determine whether they are currently
in G2 or G1 -- but they do not have too because all they are interested in
is whether there are available signals, and they always start in G2 (whose
group slot they know because of the bit in the waiter sequence. Signalers
will simply fill the right group until it is completely signaled and can
be closed (they do not switch group roles until they really have to to
decrease the likelihood of having to wait for waiters still holding a
reference on the now-closed G1).
Signalers maintain the initial size of G1 to be able to determine where
G2 starts (G2 is always open-ended until it becomes G1). They track the
remaining size of a group; when waiters cancel waiting (due to PThreads
cancellation or timeouts), they will decrease this remaining size as well.
To implement condvar destruction requirements (i.e., that
pthread_cond_destroy can be called as soon as all waiters have been
signaled), waiters increment a reference count before starting to wait and
decrement it after they stopped waiting but right before they acquire the
mutex associated with the condvar.
pthread_cond_t thus consists of the following (bits that are used for
flags and are not part of the primary value of each field but necessary
to make some things atomic or because there was no space for them
elsewhere in the data structure):
__wseq: Waiter sequence counter
* LSB is index of current G2.
* Waiters fetch-add while having acquire the mutex associated with the
condvar. Signalers load it and fetch-xor it concurrently.
__g1_start: Starting position of G1 (inclusive)
* LSB is index of current G2.
* Modified by signalers while having acquired the condvar-internal lock
and observed concurrently by waiters.
__g1_orig_size: Initial size of G1
* The two least-significant bits represent the condvar-internal lock.
* Only accessed while having acquired the condvar-internal lock.
__wrefs: Waiter reference counter.
* Bit 2 is true if waiters should run futex_wake when they remove the
last reference. pthread_cond_destroy uses this as futex word.
* Bit 1 is the clock ID (0 == CLOCK_REALTIME, 1 == CLOCK_MONOTONIC).
* Bit 0 is true iff this is a process-shared condvar.
* Simple reference count used by both waiters and pthread_cond_destroy.
(If the format of __wrefs is changed, update nptl_lock_constants.pysym
and the pretty printers.)
For each of the two groups, we have:
__g_refs: Futex waiter reference count.
* LSB is true if waiters should run futex_wake when they remove the
last reference.
* Reference count used by waiters concurrently with signalers that have
acquired the condvar-internal lock.
__g_signals: The number of signals that can still be consumed.
* Used as a futex word by waiters. Used concurrently by waiters and
signalers.
* LSB is true iff this group has been completely signaled (i.e., it is
closed).
__g_size: Waiters remaining in this group (i.e., which have not been
signaled yet.
* Accessed by signalers and waiters that cancel waiting (both do so only
when having acquired the condvar-internal lock.
* The size of G2 is always zero because it cannot be determined until
the group becomes G1.
* Although this is of unsigned type, we rely on using unsigned overflow
rules to make this hold effectively negative values too (in
particular, when waiters in G2 cancel waiting).
A PTHREAD_COND_INITIALIZER condvar has all fields set to zero, which yields
a condvar that has G2 starting at position 0 and a G1 that is closed.
Because waiters do not claim ownership of a group right when obtaining a
position in __wseq but only reference count the group when using futexes
to block, it can happen that a group gets closed before a waiter can
increment the reference count. Therefore, waiters have to check whether
their group is already closed using __g1_start. They also have to perform
this check when spinning when trying to grab a signal from __g_signals.
Note that for these checks, using relaxed MO to load __g1_start is
sufficient because if a waiter can see a sufficiently large value, it could
have also consume a signal in the waiters group.
Waiters try to grab a signal from __g_signals without holding a reference
count, which can lead to stealing a signal from a more recent group after
their own group was already closed. They cannot always detect whether they
in fact did because they do not know when they stole, but they can
conservatively add a signal back to the group they stole from; if they
did so unnecessarily, all that happens is a spurious wake-up. To make this
even less likely, __g1_start contains the index of the current g2 too,
which allows waiters to check if there aliasing on the group slots; if
there wasn't, they didn't steal from the current G1, which means that the
G1 they stole from must have been already closed and they do not need to
fix anything.
It is essential that the last field in pthread_cond_t is __g_signals[1]:
The previous condvar used a pointer-sized field in pthread_cond_t, so a
PTHREAD_COND_INITIALIZER from that condvar implementation might only
initialize 4 bytes to zero instead of the 8 bytes we need (i.e., 44 bytes
in total instead of the 48 we need). __g_signals[1] is not accessed before
the first group switch (G2 starts at index 0), which will set its value to
zero after a harmless fetch-or whose return value is ignored. This
effectively completes initialization.
Limitations:
* This condvar isn't designed to allow for more than
__PTHREAD_COND_MAX_GROUP_SIZE * (1 << 31) calls to __pthread_cond_wait.
* More than __PTHREAD_COND_MAX_GROUP_SIZE concurrent waiters are not
supported.
* Beyond what is allowed as errors by POSIX or documented, we can also
return the following errors:
* EPERM if MUTEX is a recursive mutex and the caller doesn't own it.
* EOWNERDEAD or ENOTRECOVERABLE when using robust mutexes. Unlike
for other errors, this can happen when we re-acquire the mutex; this
isn't allowed by POSIX (which requires all errors to virtually happen
before we release the mutex or change the condvar state), but there's
nothing we can do really.
* When using PTHREAD_MUTEX_PP_* mutexes, we can also return all errors
returned by __pthread_tpp_change_priority. We will already have
released the mutex in such cases, so the caller cannot expect to own
MUTEX.
Other notes:
* Instead of the normal mutex unlock / lock functions, we use
__pthread_mutex_unlock_usercnt(m, 0) / __pthread_mutex_cond_lock(m)
because those will not change the mutex-internal users count, so that it
can be detected when a condvar is still associated with a particular
mutex because there is a waiter blocked on this condvar using this mutex.
*/
static __always_inline int
__pthread_cond_wait_common (pthread_cond_t *cond, pthread_mutex_t *mutex,
clockid_t clockid,
const struct timespec *abstime)
{
const int maxspin = 0;
int err;
int result = 0;
LIBC_PROBE (cond_wait, 2, cond, mutex);
/* clockid will already have been checked by
__pthread_cond_clockwait or pthread_condattr_setclock, or we
don't use it if abstime is NULL, so we don't need to check it
here. */
/* Acquire a position (SEQ) in the waiter sequence (WSEQ). We use an
atomic operation because signals and broadcasts may update the group
switch without acquiring the mutex. We do not need release MO here
because we do not need to establish any happens-before relation with
signalers (see __pthread_cond_signal); modification order alone
establishes a total order of waiters/signals. We do need acquire MO
to synchronize with group reinitialization in
__condvar_quiesce_and_switch_g1. */
uint64_t wseq = __condvar_fetch_add_wseq_acquire (cond, 2);
/* Find our group's index. We always go into what was G2 when we acquired
our position. */
unsigned int g = wseq & 1;
uint64_t seq = wseq >> 1;
/* Increase the waiter reference count. Relaxed MO is sufficient because
we only need to synchronize when decrementing the reference count. */
unsigned int flags = atomic_fetch_add_relaxed (&cond->__data.__wrefs, 8);
int private = __condvar_get_private (flags);
/* Now that we are registered as a waiter, we can release the mutex.
Waiting on the condvar must be atomic with releasing the mutex, so if
the mutex is used to establish a happens-before relation with any
signaler, the waiter must be visible to the latter; thus, we release the
mutex after registering as waiter.
If releasing the mutex fails, we just cancel our registration as a
waiter and confirm that we have woken up. */
err = __pthread_mutex_unlock_usercnt (mutex, 0);
if (__glibc_unlikely (err != 0))
{
__condvar_cancel_waiting (cond, seq, g, private);
__condvar_confirm_wakeup (cond, private);
return err;
}
/* Now wait until a signal is available in our group or it is closed.
Acquire MO so that if we observe a value of zero written after group
switching in __condvar_quiesce_and_switch_g1, we synchronize with that
store and will see the prior update of __g1_start done while switching
groups too. */
unsigned int signals = atomic_load_acquire (cond->__data.__g_signals + g);
do
{
while (1)
{
/* Spin-wait first.
Note that spinning first without checking whether a timeout
passed might lead to what looks like a spurious wake-up even
though we should return ETIMEDOUT (e.g., if the caller provides
an absolute timeout that is clearly in the past). However,
(1) spurious wake-ups are allowed, (2) it seems unlikely that a
user will (ab)use pthread_cond_wait as a check for whether a
point in time is in the past, and (3) spinning first without
having to compare against the current time seems to be the right
choice from a performance perspective for most use cases. */
unsigned int spin = maxspin;
while (signals == 0 && spin > 0)
{
/* Check that we are not spinning on a group that's already
closed. */
if (seq < (__condvar_load_g1_start_relaxed (cond) >> 1))
goto done;
/* TODO Back off. */
/* Reload signals. See above for MO. */
signals = atomic_load_acquire (cond->__data.__g_signals + g);
spin--;
}
/* If our group will be closed as indicated by the flag on signals,
don't bother grabbing a signal. */
if (signals & 1)
goto done;
/* If there is an available signal, don't block. */
if (signals != 0)
break;
/* No signals available after spinning, so prepare to block.
We first acquire a group reference and use acquire MO for that so
that we synchronize with the dummy read-modify-write in
__condvar_quiesce_and_switch_g1 if we read from that. In turn,
in this case this will make us see the closed flag on __g_signals
that designates a concurrent attempt to reuse the group's slot.
We use acquire MO for the __g_signals check to make the
__g1_start check work (see spinning above).
Note that the group reference acquisition will not mask the
release MO when decrementing the reference count because we use
an atomic read-modify-write operation and thus extend the release
sequence. */
atomic_fetch_add_acquire (cond->__data.__g_refs + g, 2);
if (((atomic_load_acquire (cond->__data.__g_signals + g) & 1) != 0)
|| (seq < (__condvar_load_g1_start_relaxed (cond) >> 1)))
{
/* Our group is closed. Wake up any signalers that might be
waiting. */
__condvar_dec_grefs (cond, g, private);
goto done;
}
// Now block.
struct _pthread_cleanup_buffer buffer;
struct _condvar_cleanup_buffer cbuffer;
cbuffer.wseq = wseq;
cbuffer.cond = cond;
cbuffer.mutex = mutex;
cbuffer.private = private;
__pthread_cleanup_push (&buffer, __condvar_cleanup_waiting, &cbuffer);
if (abstime == NULL)
{
/* Block without a timeout. */
err = futex_wait_cancelable (
cond->__data.__g_signals + g, 0, private);
}
else
{
/* Block, but with a timeout.
Work around the fact that the kernel rejects negative timeout
values despite them being valid. */
if (__glibc_unlikely (abstime->tv_sec < 0))
err = ETIMEDOUT;
else
{
err = futex_abstimed_wait_cancelable
(cond->__data.__g_signals + g, 0, clockid, abstime,
private);
}
}
__pthread_cleanup_pop (&buffer, 0);
if (__glibc_unlikely (err == ETIMEDOUT))
{
__condvar_dec_grefs (cond, g, private);
/* If we timed out, we effectively cancel waiting. Note that
we have decremented __g_refs before cancellation, so that a
deadlock between waiting for quiescence of our group in
__condvar_quiesce_and_switch_g1 and us trying to acquire
the lock during cancellation is not possible. */
__condvar_cancel_waiting (cond, seq, g, private);
result = ETIMEDOUT;
goto done;
}
else
__condvar_dec_grefs (cond, g, private);
/* Reload signals. See above for MO. */
signals = atomic_load_acquire (cond->__data.__g_signals + g);
}
}
/* Try to grab a signal. Use acquire MO so that we see an up-to-date value
of __g1_start below (see spinning above for a similar case). In
particular, if we steal from a more recent group, we will also see a
more recent __g1_start below. */
while (!atomic_compare_exchange_weak_acquire (cond->__data.__g_signals + g,
&signals, signals - 2));
/* We consumed a signal but we could have consumed from a more recent group
that aliased with ours due to being in the same group slot. If this
might be the case our group must be closed as visible through
__g1_start. */
uint64_t g1_start = __condvar_load_g1_start_relaxed (cond);
if (seq < (g1_start >> 1))
{
/* We potentially stole a signal from a more recent group but we do not
know which group we really consumed from.
We do not care about groups older than current G1 because they are
closed; we could have stolen from these, but then we just add a
spurious wake-up for the current groups.
We will never steal a signal from current G2 that was really intended
for G2 because G2 never receives signals (until it becomes G1). We
could have stolen a signal from G2 that was conservatively added by a
previous waiter that also thought it stole a signal -- but given that
that signal was added unnecessarily, it's not a problem if we steal
it.
Thus, the remaining case is that we could have stolen from the current
G1, where "current" means the __g1_start value we observed. However,
if the current G1 does not have the same slot index as we do, we did
not steal from it and do not need to undo that. This is the reason
for putting a bit with G2's index into__g1_start as well. */
if (((g1_start & 1) ^ 1) == g)
{
/* We have to conservatively undo our potential mistake of stealing
a signal. We can stop trying to do that when the current G1
changes because other spinning waiters will notice this too and
__condvar_quiesce_and_switch_g1 has checked that there are no
futex waiters anymore before switching G1.
Relaxed MO is fine for the __g1_start load because we need to
merely be able to observe this fact and not have to observe
something else as well.
??? Would it help to spin for a little while to see whether the
current G1 gets closed? This might be worthwhile if the group is
small or close to being closed. */
unsigned int s = atomic_load_relaxed (cond->__data.__g_signals + g);
while (__condvar_load_g1_start_relaxed (cond) == g1_start)
{
/* Try to add a signal. We don't need to acquire the lock
because at worst we can cause a spurious wake-up. If the
group is in the process of being closed (LSB is true), this
has an effect similar to us adding a signal. */
if (((s & 1) != 0)
|| atomic_compare_exchange_weak_relaxed
(cond->__data.__g_signals + g, &s, s + 2))
{
/* If we added a signal, we also need to add a wake-up on
the futex. We also need to do that if we skipped adding
a signal because the group is being closed because
while __condvar_quiesce_and_switch_g1 could have closed
the group, it might stil be waiting for futex waiters to
leave (and one of those waiters might be the one we stole
the signal from, which cause it to block using the
futex). */
futex_wake (cond->__data.__g_signals + g, 1, private);
break;
}
/* TODO Back off. */
}
}
}
done:
/* Confirm that we have been woken. We do that before acquiring the mutex
to allow for execution of pthread_cond_destroy while having acquired the
mutex. */
__condvar_confirm_wakeup (cond, private);
/* Woken up; now re-acquire the mutex. If this doesn't fail, return RESULT,
which is set to ETIMEDOUT if a timeout occured, or zero otherwise. */
err = __pthread_mutex_cond_lock (mutex);
/* XXX Abort on errors that are disallowed by POSIX? */
return (err != 0) ? err : result;
}
源碼比較難懂
但可以看見有調用
err = __pthread_mutex_unlock_usercnt (mutex, 0);
...
err = __pthread_mutex_cond_lock (mutex);
應該是調用pthread_cond_wait后先釋放鎖,然后加入waiter隊列等待signal,如果有signal喚醒了該線程則后續執行重新上鎖