簡單線程了解
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> //創建兩個線程,分別對兩個全變量進行++操作,判斷兩個變量是否相等,不相等打印
int a = 0;
int b = 0;
// 未初始化 和0初始化的成員放在bbs
pthread_mutex_t mutex;
void* route()
{
while(1) //初衷不會打印
{
a++;
b++;
if(a != b)
{
printf("a =%d, b = %d\n", a, b);
a = 0;
b = 0;
}
}
}
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, route, NULL);
pthread_create(&tid2, NULL, route, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
段代碼的運行結果優點出乎我們的預料:

我們預計的結構應該是不會打印的,而這里去打印出了我們意想不到的結果。連相等的數據都打印了出來,為什么會出現這樣的情況呢?
解釋:兩個線程互相搶占CPU資源,一個線程對全局變量做了++操作之后,還沒來得及比較輸出操作,另一個線程搶占CPU,進行比較打印輸出。為了避免這樣的情況,就需要用到下面介紹的互斥鎖。
互斥量(鎖):用於保護關鍵的代碼段,以確保其獨占式的訪問。
1.定義互斥量: pthread_mutex_t mutex;
2.初始化互斥量: pthread_mutex_init(&mutex, NULL); //第二個參數不研究置NULL; //初始化為 1 (僅做記憶)
3.上鎖 pthread_mutex_lock(&mutex); 1->0; 0 等待
4.解鎖 pthread_mutex_unlock(&mutex); 置1 返回
5.銷毀 pthread_mutex_destroy(&mutex);
返回值:若成功返回0,若出錯返回錯誤編號。
說明: 互斥鎖,在多個線程對共享資源進行訪問時,在訪問共享資源前對互斥量進行加鎖,在訪問完再進行解鎖,在互斥量加鎖后其他的線程將阻塞,直到當前的線程訪問完畢並釋放鎖。如果釋放互斥鎖時有多個線程阻塞,所有阻塞線程都會變成可運行狀態,第一個變成可運行狀態的線程可以對互斥量加鎖。這樣就保證了每次只有一個線程訪問共享資源。
至此,我們好像能通過互斥鎖解決上面的問題:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int a = 0;
int b = 0;
// 未初始化 和0初始化的成員放在bbs
pthread_mutex_t mutex;
void* route()
{
while(1) //初衷不會打印
{
pthread_mutex_lock(&mutex);
a++;
b++;
if(a != b)
{
printf("a =%d, b = %d\n", a, b);
a = 0;
b = 0;
}
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, route, NULL);
pthread_create(&tid2, NULL, route, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
現有如下場景:線程1和線程2,線程1執行函數A,線程2執行函數B,現只使用一把鎖,分別對A,B函數的執行過程加鎖和解鎖。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h> //線程的取消動作發生在加鎖和解鎖過程中時,當發生線程2取消后而沒有進行解鎖時,就會出現線程1將一直阻塞
pthread_mutex_t mutex;
void* odd(void* arg)
{
int i = 1;
for(; ; i+=2)
{
pthread_mutex_lock(&mutex);
printf("%d\n", i);
pthread_mutex_unlock(&mutex);
}
}
void* even(void* arg)
{
int i = 0;
for(; ; i+=2)
{
pthread_mutex_lock(&mutex);
printf("%d\n", i);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t t1, t2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, even, NULL);
pthread_create(&t2, NULL, odd, NULL);
//pthread_create(&t3, NULL, even, NULL);
sleep(3);
pthread_cancel(t2); //取消線程2,這個動作可能發生在線程2加鎖之后和解鎖之前
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
一種極限情況是:線程2的取消發生在線程2的解鎖之前,那么就會導致因為鎖沒解開,而線程1無法繼續運行。
解決這樣的問題我們可以用到下面的宏函數:
宏: //注冊線程回調函數,可用來防止線程取消后沒有解鎖的問題
void pthread_cleanup_push(void (*routine)(void *), //回調函數
void *arg); //回調函數的參數
//回調函數執行時機
1.pthread_exit
2.pthread_cancel
3.cleanaup_pop參數不為0,當執行到cleaup_pop時,調用回調函數
void pthread_cleanup_pop(int execute);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h> //線程的取消動作發生在加鎖和解鎖過程中時,當發生線程2取消后而沒有進行解鎖時,就會出現線程1將一直阻塞
pthread_mutex_t mutex;
void callback(void* arg) //在cancel中進行解鎖
{
printf("callback\n");
sleep(1);
pthread_mutex_unlock(&mutex);
}
void* odd(void* arg)
{
int i = 1;
for(; ; i+=2)
{
pthread_cleanup_push(callback, NULL);//因為調用了cancel函數,從而觸發了回調函數。
pthread_mutex_lock(&mutex);
printf("%d\n", i);
pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
}
}
void* even(void* arg)
{
int i = 0;
for(; ; i+=2)
{
pthread_mutex_lock(&mutex);
printf("%d\n", i);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t t1, t2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, even, NULL);
pthread_create(&t2, NULL, odd, NULL);
//pthread_create(&t3, NULL, even, NULL);
sleep(3);
pthread_cancel(t2); //取消線程2,這個動作可能發生在線程2加鎖之后和解鎖之前
//pthread_mutex_unlock(&mutex); 有問題,如果執行even的程序有兩個,而一個取消線程的函數執行時正好t3函數阻塞,就會導致t3和t1同時在執行even
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
注意:
1.不要銷毀一個已經加鎖的互斥量,銷毀的互斥量確保后面不會再有線程使用。
2.上鎖和解鎖函數要成對的使用
3.選擇合適的鎖的粒度(數量)。如果粒度太粗,就會出現很多線程阻塞等待相同鎖,源自並發性的改善微乎其微。如果鎖的粒度太細,那么太多的鎖的開銷會使系統的性能受到影響,而且代碼會變得相當復雜。
4.加鎖要加最小(范圍)鎖,減少系統負擔
使用互斥鎖一定要注意避免死鎖:《Linux高性能服務器編程》 14.5.3 介紹了兩個互斥量因請求順序產生死鎖問題
如果線程試圖對同一個互斥量加鎖兩次,那么它自身就會陷入死鎖狀態,使用互斥量時,還有其他更不明顯的方式也能產生死鎖。例如,程序中使用多個互斥量時,如果允許一個線程一直占有第一個互斥量,並且在試圖鎖住第二個互斥量時處於阻塞狀態,但是擁有第二個互斥量的線程也在試圖鎖住第一個互斥量,這時就會發生死鎖。因為兩個線程都在相互請求另一個線程擁有的資源,所以這兩個線程都無法向前運行,於是就產生死鎖。
可以通過小心地控制互斥量加鎖的順序來避免死鎖的發生。例如,假設需要對兩個互斥量A和B同時加鎖,如果所有線程總是在對互斥量B加鎖之前鎖住互斥量A,那么使用這兩個互斥量不會產生死鎖(當然在其他資源上仍可能出現死鎖);類似地,如果所有的線程總是在鎖住互斥量A之前鎖住互斥量B,那么也不會發生死鎖。只有在一個線程試圖以與另一個線程相反的順序鎖住互斥量時,才可能出現死鎖。
為了應對死鎖,在實際的編程中除除了加上同步互斥量之外,還可以通過以下三原則來避免寫出死鎖的代碼:
1>短:寫的代碼盡量簡潔
2>平:代碼中沒有復雜的函數調用
3>快:代碼的執行速度盡可能快
自旋鎖: 應用在實時性要求較高的場合(缺點:CPU浪費較大)
pthread_mutex_spin;
pthread_spin_lock() ; //得不到時,進入忙等待,不斷向CPU進行詢問請求
pthread_spin_unlock();
pthread_spin_destroy(pthread_spinlock_t *lock);
pthread_spin_init(pthread_spinlock_t *lock, int pshared);
讀寫鎖(共享-獨占鎖):應用場景---大量的讀操作 較少的寫操作
注意:讀讀共享, 讀寫互斥,寫優先級高(同時到達)
1. pthread_rwlock_t rwlock;//定義
2.int pthread_rwlock_init()//初始化
3.pthread_rwlock_rdlock()//pthread_rwlock_wrlock//讀鎖/寫鎖
4.pthread_rwlock_unlock() // 解鎖
5.int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//銷毀鎖
返回值:成功返回0,出錯返回錯誤編號
說明:不管什么時候要增加一個作業到隊列中或者從隊列中刪除作業,都用寫鎖,
不管何時搜索隊列,首先獲取讀模式下的鎖,允許所有的工作線程並發的搜索隊列。在這樣的情況下只有線程
搜索隊列的頻率遠遠高於增加或刪除作業時,使用讀寫鎖才可能改善性能。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h> //創建8個線程,3個寫線程,5個讀線程
pthread_rwlock_t rwlock;
int counter = 0;
void* readfunc(void* arg)
{
int id = *(int*)arg;
free(arg);
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read thread %d : %d\n", id, counter);
pthread_rwlock_unlock(&rwlock);
usleep(100000);
}
}
void* writefunc(void* arg)
{
int id = *(int*)arg;
free(arg);
while(1)
{
int t = counter;
pthread_rwlock_wrlock(&rwlock);
printf("write thread %d : t= %d, %d\n", id, t, ++counter);
pthread_rwlock_unlock(&rwlock);
usleep(100000);
}
}
int main()
{
pthread_t tid[8];
pthread_rwlock_init(&rwlock, NULL);
int i = 0;
for(i = 0; i < 3; i++)
{
int* p =(int*) malloc(sizeof(int));
*p = i;
pthread_create(&tid[i], NULL, writefunc, (void*)p);
}
for(i = 0; i < 5; i++)
{
int* p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&tid[3+i], NULL, readfunc, (void*)p);
}
for(i = 0; i < 8; i++)
{
pthread_join(tid[i], NULL);
}
pthread_rwlock_destroy(&rwlock);
return 0;
}
條件變量: 如果說互斥鎖是用於同步線程對共享數據的訪問的化,那么條件變量這是用於在線程之間同步共享數據的值。條件變量提供了一種線程間的通信機制:當某個共享數據達到某個值的時候,喚醒等待這個共享數據的線程
1.定義條件變量 pthread_cond_t cond;
2.初始化 pthread_cond_init(&cond, NULL);
3.等待條件 pthread_cond_wait(&cond, &mutex);
mutex :如果沒有在互斥環境,形同虛設
在互斥環境下:wait函數將mutex置1,wait返回,mutex恢復成原來的值
4.修改條件 pthread_cond_signal(&cond);
5.銷毀條件 pthread_cond_destroy(&cond);
//規范寫法:
pthread_mutex_lock();
while(條件不滿足)
pthread_cond_wait();
//為什么會使用while?
//因為pthread_cond_wait是阻塞函數,可能被信號打斷而返回(喚醒),返回后從當前位置
//向下執行, 被信號打斷而返回(喚醒),即為假喚醒,繼續阻塞
pthread_mutex_unlock();
pthread_mutex_lock();
pthread_cond_signal(); //信號通知 ---- 如果沒有線程在等待,信號會被丟棄(不會保存起來)。
pthread_mutex_unlock();
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h> //創建兩個線程一個wait print,一個signal sleep()
pthread_cond_t cond;
pthread_mutex_t mutex;
void* f1(void* arg)
{
while(1)
{
pthread_cond_wait(&cond, &mutex);
printf("running!\n");
}
}
void* f2(void* arg)
{
while(1)
{
sleep(1);
pthread_cond_signal(&cond);
}
}
int main()
{
pthread_t tid1, tid2;
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, f1, NULL);
pthread_create(&tid2, NULL, f2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
System V //基於內核持續性
信號量: POSIX //基於文件持續性的信號量
1.定義信號量: sem_t sem;
2,初始化信號量: sem_init(sem_t* sem,
int shared, //0表示進程內有多少個線程使用
int val); //信號量初值
3.PV操作 int sem_wait(sem_t* sem); //sem--;如果小於0,阻塞 P操作
int sem_post(sem_t* sem); //sem++; V操作
4.銷毀 sem_destroy(sem_t* sem);
信號量實現生產者消費者模型:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//倉庫中裝產品編號,沒裝產品的位置,置為-1,裝了的地方置為產品的編號
#define PRO_COUNT 3
#define CON_COUNT 2
#define BUFSIZE 5
sem_t sem_full; //標識可生產的產品個數
sem_t sem_empty; //表示可消費的產品個數
pthread_mutex_t mutex; //互斥量
int num = 0; //產品編號
int buf[BUFSIZE]; //倉庫
int wr_idx; //寫索引
int rd_idx; //讀索引
void* pro(void* arg)
{
int i = 0;
int id = *(int*)arg;
free(arg);
while(1)
{
sem_wait(&sem_full); //先判斷倉庫是否滿
pthread_mutex_lock(&mutex); //互斥的訪問具體的倉庫的空閑位置
printf("%d生產者開始生產%d\n", id, num);
for(i = 0; i < BUFSIZE; i++)
{
printf("\tbuf[%d]=%d", i, buf[i]);
if(i == wr_idx)
{
printf("<=====");
}
printf("\n");
}
buf[wr_idx] = num++; //存放產品
wr_idx = (wr_idx + 1) % BUFSIZE;
printf("%d生產者結束生產\n", id);
pthread_mutex_unlock(&mutex);
sem_post(&sem_empty);
sleep(rand()%3);
}
}
void* con(void* arg)
{
int i = 0;
int id = *(int*)arg;
free(arg);
while(1)
{
sem_wait(&sem_empty);
pthread_mutex_lock(&mutex);
printf("%d消費者開始消費%d\n", id, num);
for(i = 0; i < BUFSIZE; i++)
{
printf("buf[%d]=%d", i, buf[i]);
if(i == rd_idx)
{
printf("=====>");
}
printf("\n");
}
int r = buf[rd_idx];
buf[rd_idx] = -1;
rd_idx = (rd_idx+1)%BUFSIZE;
sleep(rand()%4);
printf("%d\n消費者消費完%d\n", id, r);
pthread_mutex_unlock(&mutex);
sem_post(&sem_full);
sleep(rand()%2);
}
}
int main()
{
pthread_t tid[PRO_COUNT+CON_COUNT];
pthread_mutex_init(&mutex, NULL); //初始化
sem_init(&sem_empty, 0, 0);
sem_init(&sem_full, 0, BUFSIZE);
srand(getpid());
int i = 0;
for(i = 0; i < BUFSIZE; i++) //初始化倉庫 -1表示沒有品
buf[i] = -1;
for(i = 0; i < PRO_COUNT; i++) //產生生產者
{
int *p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&tid[i], NULL, pro, p);
}
for(i = 0; i < CON_COUNT; i++)
{
int *p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&tid[i+CON_COUNT], NULL, con, p);
}
for(i = 0; i < PRO_COUNT + CON_COUNT; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&mutex); //銷毀
sem_destroy(&sem_empty);
sem_destroy(&sem_full);
return 0;
}
拓展學習:
樂觀鎖和悲觀鎖?
樂觀鎖:
在關系數據庫管理系統里,樂觀並發控制(又名”樂觀鎖”,Optimistic Concurrency Control,縮寫”OCC”)是一種並發控制的方法。它假設多用戶並發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分數據。在提交數據更新之前,每個事務會先檢查在該事務讀取數據后,有沒有其他事務又修改了該數據。如果其他事務有更新的話,正在提交的事務會進行回滾。
樂觀並發控制的事務包括以下階段:
1. 讀取:事務將數據讀入緩存,這時系統會給事務分派一個時間戳。
2. 校驗:事務執行完畢后,進行提交。這時同步校驗所有事務,如果事務所讀取的數據在讀取之后又被其他事務修改,則產生沖突,事務被中斷回滾。
3. 寫入:通過校驗階段后,將更新的數據寫入數據庫。
優點和不足:
樂觀並發控制相信事務之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。但如果直接簡單這么做,還是有可能會遇到不可預期的結果,例如兩個事務都讀取了數據庫的某一行,經過修改以后寫回數據庫,這時就遇到了問題。
悲觀鎖:
在關系數據庫管理系統里,悲觀並發控制(又名”悲觀鎖”,Pessimistic Concurrency Control,縮寫”PCC”)是一種並發控制的方法。它可以阻止一個事務以影響其他用戶的方式來修改數據。如果一個事務執行的操作讀某行數據應用了鎖,那只有當這個事務把鎖釋放,其他事務才能夠執行與該鎖沖突的操作。
優點和不足:悲觀並發控制實際上是“先取鎖再訪問”的保守策略,為數據處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增加產生死鎖的機會;另外,在只讀型事務處理中由於不會產生沖突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以處理那行數
系統最多能夠創建多少個線程? (一般以實測為准,但根據每次開辟的棧的大小不同,測試結果也會不同)。
一個是直接在命令行查看 cat /proc/sys/kernel/threads-max 我的電腦顯示是 7572
另一個是自己計算 用戶空間大小3G 即是3072M/8M棧空間 = 380
第三個寫程序: 跑到32754(理論值 32768)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> //創建線程
void* foo(void* arg)
{
}
int main()
{
int count = 0;
pthread_t thread;
while(1)
{
if(pthread_create(&thread, NULL, foo, NULL) != 0)
return 1;
count++;
printf("MAX = %d\n", count);
}
return 0;
}
