Linux 學習筆記—執行緒同步之讀寫鎖、自旋鎖、屏障
3.2.1 讀寫鎖 讀寫鎖和互斥體類似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個執行緒可以對其加鎖。而讀寫鎖可以有3個狀態,讀模式下鎖住狀態,寫模式下鎖住狀態,不加鎖狀態。一次只有一個執行緒可以佔有寫模式的讀寫鎖,但是多個執行緒可以同時佔用讀模式的讀寫鎖。讀寫鎖適合對資料結構讀的次數遠大於寫的情況。 當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會被阻塞。當讀寫鎖是讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權,但是任何希望以寫模式對此鎖進行加鎖的執行緒都會阻塞,直到所有的執行緒釋放它們的讀鎖為止。 #include<pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockaddr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock)
例項1 讀寫鎖
/** * 兩個讀執行緒讀取資料,一個寫執行緒更新資料 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #define READ_THREAD 0 #define WRITE_THREAD 1 int g_data = 0; pthread_rwlock_t g_rwlock; void *func(void *pdata) { int data = (int)pdata; printf("data=%d\n",data); while (1) { if (READ_THREAD == data) { pthread_rwlock_rdlock(&g_rwlock); printf("-----%d------ %d\n", pthread_self(), g_data); sleep(1); pthread_rwlock_unlock(&g_rwlock); sleep(1); } else { pthread_rwlock_wrlock(&g_rwlock); g_data++; printf("g_data=%d\n",g_data); printf("add the g_data\n"); pthread_rwlock_unlock(&g_rwlock); sleep(1); } } return NULL; } int main(int argc, char **argv) { pthread_t t1, t2, t3; pthread_rwlock_init(&g_rwlock, NULL); pthread_create(&t1, NULL, func, (void *)READ_THREAD);//0 pthread_create(&t2, NULL, func, (void *)READ_THREAD);//0 pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);//1 pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_rwlock_destroy(&g_rwlock); return 0; }
3.2.2 自旋鎖(汽車等紅綠燈不熄火) 自旋鎖和互斥量類似,但它不是通過休眠使程序阻塞,而是在獲取鎖之前一直處於忙等(自旋)狀態,自旋鎖可用於下面的情況:鎖被持有的時間短,並且執行緒不希望再重新排程上花費太多的成本。自旋鎖通常作為底層原語用於實現其他型別的鎖。根據他們所基於的系統架構,可以通過使用測試並設定指令有效地實現。當然這裡說的有效也還是會導致CPU資源的浪費:當執行緒自旋鎖變為可用時,CPU不能做其他任何事情,這也是自旋鎖只能夠被只有一小段時間的原因。 #include <pthread.h> int pthread_spin_init(pthread_spinlock_t *lock, int pshared); int pthread_spin_destroy(pthread_spinlock_t *lock); pshared引數表示程序共享屬性,表明自旋鎖是如何獲取的,如果它設為PTHREAD_PROCESS_SHARED,則自旋鎖能被可以訪問鎖底層記憶體的執行緒所獲取,即使那些執行緒屬於不同的程序。否則pshared引數設為PTHREAD_PROCESS_PROVATE,自旋鎖就只能被初始化該鎖的程序內部的執行緒訪問到。 #include <pthread.h> int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock); 如果自旋鎖當前在解鎖狀態,pthread_spin_lock函式不要自旋就可以對它加鎖,試圖對沒有加鎖的自旋鎖進行解鎖,結果是未定義的。需要注意,不要在持有自旋鎖情況下可能會進入休眠狀態的函式,如果呼叫了這些函式,會浪費CPU資源,其他執行緒需要獲取自旋鎖需要等待的時間更長了。
例項2 自旋鎖
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_spinlock_t g_lock;
int g_data = 0;
void *func(void *arg)
{
while (1) {
pthread_spin_lock(&g_lock);
g_data++;
printf("----------- %d\n", g_data);
sleep(1);
pthread_spin_unlock(&g_lock);
}
}
int main(int argc, char **argv)
{
pthread_t tid;
pthread_spin_init(&g_lock, PTHREAD_PROCESS_PRIVATE);
pthread_create(&tid, NULL, func, NULL);
pthread_create(&tid, NULL, func, NULL);
pthread_create(&tid, NULL, func, NULL);
pthread_join(tid, NULL);
return 0;
}
3.2.3 屏障(瞭解) 屏障是使用者協調多個執行緒並行工作的同步機制,屏障允許每個執行緒等待,直到所有合作的執行緒都到達某一點,然後從該點出繼續執行。pthread_join其實就是一種屏障,允許一個執行緒等待,直到另一個執行緒退出。但是屏障物件的概念更廣,它們允許任意數量的執行緒等待,直到所有的執行緒完成處理工作,而執行緒不需要退出,所有執行緒達到屏障後可以繼續工作。 #include <pthread.h> int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count); int pthread_barrier_destroy(pthread_barrier_t *barrier); // 成功返回0,否則返回錯誤編號 初始化屏障時,可以使用count引數指定,在允許所有執行緒繼續執行前,必須達到屏障的執行緒數目。attr指定屏障屬性,NULL為預設屬性。 #include <pthread.h> int pthread_barrier_wait(pthread_barrier_t *barrier); // 成功返回0,否則返回錯誤編號 可以使用pthread_barrier_wait函式來表明,執行緒已完成工作,準備等所有其他執行緒趕過來。呼叫pthread_barrier_wait的執行緒在屏障計數未滿足條件時,會進入休眠狀態。如果該執行緒是最後一個呼叫pthread_barrier_wait的執行緒,則所有的執行緒會被喚醒。 一旦到達屏障計數值,而且執行緒處於非阻塞狀態,屏障就可以被重複使用。
例項3 屏障
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_barrier_t g_barrier;
void *func(void *arg)
{
int id = (int )arg;
if (id == 0) {
printf("thread 0\n");
sleep(1);
pthread_barrier_wait(&g_barrier);
printf("thread 0 come...\n");
}
else if (id == 1) {
printf("thread 1\n");
sleep(2);
pthread_barrier_wait(&g_barrier);
printf("thread 1 come...\n");
}
else if (id == 2) {
printf("thread 2\n");
sleep(3);
pthread_barrier_wait(&g_barrier);
printf("thread 2 come...\n");
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t1, t2, t3;
pthread_barrier_init(&g_barrier, NULL, 3);
pthread_create(&t1, NULL, func, (void *)0);
pthread_create(&t2, NULL, func, (void *)1);
pthread_create(&t3, NULL, func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
return 0;
}
補充:管道在多線之間通訊實現:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
Thread *m_Threads;
static int threadcount = 1;
void* work_thread(void* argc)
{
Thread* param = (Thread*) argc;
printf("childthread_tid=%lu\n", param->tid);
int contant = 0;
//sleep(2);
printf("childthread--read return %d\n",read(param->notifyReceiveFd, &contant, sizeof(int)));
printf("childthread--read from pipe %d\n", contant);
}
int main(int argc, char** argv)
{
//在主執行緒和子執行緒之間建立管道
m_Threads = malloc(sizeof(Thread) * threadcount);
int fds[2];
if( pipe(fds) )
{
perror("create pipe error");
}
m_Threads[0].notifyReceiveFd = fds[0];
pthread_create(&m_Threads[0].tid, NULL, work_thread, (void*)&m_Threads[0]);
printf("mainthread_tid=%lu\n", m_Threads[0].tid);
int contant = 1;
// sleep(2);
printf("mainthread--write %d to pipe\n", contant);
printf("mainthread--write return %d\n",write(fds[1], &contant, sizeof(int)));
pthread_join(m_Threads[0].tid, NULL);
close(fds[0]);
close(fds[1]);
return 0;
}