線程的幾種鎖及基本操作


我們先來看一段代碼:

#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;
}

轉自:(9條消息) 線程的幾種鎖及基本操作_czf的編程工坊-CSDN博客_線程鎖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM