天天看點

Linux系統程式設計之線程深度詳解(有執行個體)

線程基礎

:什麼是多線程

計算機有線程
    計算機:8核16線程
    迅雷:一次下載下傳多個任務
8核16線程的CPU
跟 8核8線程的CPU有啥差別
8核---8個人---8程序
8線程---8個活---8個main
一個程序幹了兩個main函數的時
           

這個概念在哪裡出現過呢?

32的FreeRTOS

至始至終有幾個主函數?

1個main–1個程序

FreeRTOS的任務

相當于線程

說白點就是在一個主函數中

再開辟多個同時進行的任務(線程)

2:為什麼要有線程

/******************************/

有了程序

為什麼還非要開辟線程

DHT11 蜂鳴器 LED

3個程序

1個程序3個線程

程序的建立需不需要占用系統資源

空間、CPU複制資源

PID

線程是在一個程序中建立

相對來說,線程啟動的更加快

建立的也快

線程的切換也是大大的快于程序的切換

程序的通信:

管道

信号量

消息郵箱

共享記憶體

線程間的通信:

FreeRTOS:

全局變量

不管哪個單片機

上的單片機微作業系統

通信都是全局變量

更簡單

同步和互斥

*******************************************************
線程建立函數:
int pthread_create(pthread_t * thread,
              const pthread_attr_t * attr,
              void *(*start_routine)(void*), void *arg);
thread:線程的ID
attr:線程的屬性預設直接給NULL
start_routine:函數指針
                參數  void *
                傳回值 void *
arg  : 傳給線程函數的參數
*********************************************************
線程正常結束函數:
pthread_exit(void *value_ptr);
value_ptr可傳值,pthread_join的第二個參數接收

********************************************************
等待線程結束函數:
int pthread_join(pthread_t thread, void **value_ptr);
thread:等待的線程ID
void **value_ptr:
線程正常退出時傳回的參數,預設可NULL,也可搭配pthread_exit用
           

okk我們先建立兩個線程,分别讓他們循環輸出:

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int main(void)
{
    pthread_t pthread_id[2];
    //char *buf;
    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],NULL);//(void **)&buf
    pthread_join(pthread_id[1],NULL);
    //printf("%s\n",buf);
    return 0;
}
void * text1(void * arg)
{
 for(int i=0;i<300;i++)
 {
    /*if(i==15)
     {
        pthread_exit("hi");
     }*/ 
     printf("this is pthread1\n");
 }
}
void * text2(void * arg)
{
 for(int i=0;i<300;i++)
 {
     printf("this is pthread2\n");
 }
}
           
編譯運作:
gcc pthread_text.c -lpthread
./a.out 
           
Linux系統程式設計之線程深度詳解(有執行個體)

可以看到1,2是穿插一起的,這就是線程的并行,并不是一個線程運作完了再去運作另一個線程,Linux是分時系統。是1線程跑一會,2線程跑一會,沒有規律的。

現在我們已将建立和等待結束函數測試完畢了,下面我們看一下pthread_exit函數:還是上面的代碼,我讓1線程循環15次後正常結束,将結束内容設定為hi,pthread_join第二個參數接收。

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int main(void)
{
    pthread_t pthread_id[2];
    char *buf;
    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],(void **)&buf);//注意資料類型
    pthread_join(pthread_id[1],NULL);
    printf("%s\n",buf);
    return 0;
}
void * text1(void * arg)
{
 for(int i=0;i<300;i++)
 {
    if(i==15)
     {
        pthread_exit("hi");
     } 
     printf("this is pthread1\n");
 }
}
void * text2(void * arg)
{
 for(int i=0;i<300;i++)
 {
     printf("this is pthread2\n");
 }
}
           

運作結果你會發現,線程1列印15次後就結束了,主函數輸出線程1結束内容hi

Linux系統程式設計之線程深度詳解(有執行個體)
函數功能:取消一個正在運作的線程
函數原型:int pthread_cancel(pthread_t thread);        
函數參數: 
        thread:你需要取消的線程ID
函數傳回值:  
        0  成功
        -1 失敗
***********************************************************

           

這個函數就不示範了。。。。一看就明白咋用了把。

咳咳。。。。。下面會用到!

函數功能:設定一個清理函數
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
這兩個函數必須成對出現
參數:
routine:注冊清理的函數的指針
arg:傳遞給清理函數的參數
execute:決定這個清理函數是否被調用
          0--不調用
          非0-調用
注意:采用先入後出的棧結構管理,兩個函數之間的程式段中的終止動作
(包括調用pthread_exit()和異常終止(其他程序使用pthread_cancel取
消目前程序) 不包含return)都将執行
           

上代碼:

建立倆線程,每個線程循環15次列印,線程1在第8次時候調用pthread_cancel殺死這個程序。調用清理函數

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);//線程1
void * text2(void * arg);//線程2
void * clean(void * arg);//清理函數
int main(void)
{
    pthread_t pthread_id[2];

    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],NULL);
    pthread_join(pthread_id[1],NULL);

    return 0;
}
void * text1(void * arg)
{
 pthread_cleanup_push(clean,NULL);//注冊清理函數
 for(int i=0;i<15;i++)
 {
    if(i==7)
     {
       pthread_cancel(pthread_self());
     } 
     printf("this is pthread1\n");
 }
 pthread_cleanup_pop(1);//調用清理函數
}

void * text2(void * arg)
{
 for(int i=0;i<15;i++)
 {
     printf("this is pthread2\n");
 }
}

void * clean(void * arg)
{
    printf("我是線程1的清理函數\n");
}
           

運作結果:

Linux系統程式設計之線程深度詳解(有執行個體)

可以看到在第8次時候,調用pthread_cancel殺死了1線程,進入了清理函數。有人會問這個清理函數有什麼用呢?加入你這個線程死于異常,會不會照成資料的異常,比如你在這個線程中用到了全局變量,那麼這個全局變量因為線程的不正常退出,儲存的内容是垃圾值。會不會對你程式照成嚴重的影響呢?你的全局變量肯定不可能是就這一個線程用吧,萬一另一個線程需要這個異常線程全局變量中儲存的結果呢。哪整個程式是不是就出現了大錯誤了,如果使用清理函數,我在清理函數裡面将這個全局變量給初始化為0,在調用的地方加個判斷,是不是可以避免很多問題????

okk…我們繼續下面的講解。

線程間的通信

線程中的通信,完全可以用程序中的IPC通信方法經行通信。

不是特别情況下一般不用。

一個全局變量解決所有問題。

我們講一個還算常用的吧:線程中的信号通信

信号:
    在多線程中你的信号無論在哪個線程中注冊
    隻要該程序得到信号都會去處理
    信号的處理函數:
    pthread_kill(線程id,信号)
           
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
void hello(int num)
{
	printf("hello fun!!!\n");
}
void *fun(void *data)
{
	signal(3,hello);
	while(1)
	{
		printf("fun!!!\n");
		sleep(1);
	}
	return NULL;
}

//main線程   子線程  
int main()
{
	pthread_t id;//unsigned long
	pthread_create(&id,NULL,fun,NULL);
	while(1)
	{
		sleep(1);//有時間去注冊信号操作函數
		pthread_kill(id,3);
	}
	pthread_join(id,NULL);
	return 0;
}

           
Linux系統程式設計之線程深度詳解(有執行個體)

線程的互斥鎖

舉個不太恰當的例子:

互斥鎖
    什麼是互斥
        隻能有一方存在
        要麼你存在
        要麼我存在
        要麼你用
        要麼我用
    什麼是鎖
        大門鎖
        測試需要維修
        維修人員把門鎖了
            進去維修了
        這段時間你能進去上廁所?
        隻有當維修人員搞完了
            解鎖完畢
        你才能去上廁所
            你上廁所的時候希不希望别去跟你
            一起在廁所上廁所
        你一般會把門鎖上
           

那麼互斥鎖有什麼用呢?

互斥鎖就是保證每次隻有一個操作在運作。舉個例子。

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int i=0;
int main(void)
{
    pthread_t pthread_id[2];
    //char *buf;
    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],NULL);//(void **)&buf
    pthread_join(pthread_id[1],NULL);
    //printf("%s\n",buf);
    return 0;
}
void * text1(void * arg)
{
  while(i<1000)
  {
      i++;
    printf("text1 i=%d\n",i);
  }
  
}
void * text2(void * arg)
{
    while(i<1000)
    {
     i++;
    printf("text2 i=%d\n",i);
    }
  
}
           

我定義了一個全局變量i,在1、2兩個線程中做++,假如在沒有互斥鎖的條件下,這兩個線程就沒法保證一次循環的完整。

比如:

text1
    i++;  
    printf("text1 i=%d\n",i);
           
text2:
	i++;
	printf("text1 i=%d\n",i);
           

可能text1運作一半又跑去運作text2去了,可能會出現一些問題?

上面的代碼你執行一下會發現:

Linux系統程式設計之線程深度詳解(有執行個體)

這個2是怎麼來的呢?16去哪裡了呢。是不是程式就出現了bug。

我們如何保證每一次每個線程都完整運作完才可以進行跳轉呢。

這就是需要互斥鎖了。

互斥鎖的建立:
動态建立:
pthread_mutex_init();
參數1:參數1:描述鎖的變量 pthread_mutex_t mutex;
參數2:PTHREAD_MUTEX_INITIALIZER:建立快速互斥鎖
       函數傳入值 Mutexattr
        PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:建立遞歸互斥鎖
       PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:建立檢錯互斥鎖
       NULL預設快速互斥鎖
靜态建立:
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//靜态建立快速
互斥鎖

加鎖:
	int pthread_mutex_lock(pthread_mutex_t *mutex);//成功就加鎖
	成功不成功就等待
    int pthread_mutex_trylock(pthread_mutex_t *mutex);成功就加
    鎖,不成功就傳回錯誤----非阻塞
解鎖:
	int pthread_mutex_unlock(pthread_mutex_t *mutex);
銷毀鎖:
	int pthread_mutex_destroy(pthread_mutex_t *mutex);

重點提示:
小心死鎖:在同一個線程中,第一次lock,沒問題,然後沒有unlock,又
lock一次,會阻塞,且一直阻塞下去
           

下面我們用鎖來經行代碼保護:

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
void * text1(void * arg);
void * text2(void * arg);
int i=0;
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
int main(void)
{
    pthread_t pthread_id[2];
    //char *buf;
    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],NULL);//(void **)&buf
    pthread_join(pthread_id[1],NULL);
    //printf("%s\n",buf);
    return 0;
}
void * text1(void * arg)
{
  while(i<1000)
  {
    pthread_mutex_lock(&lock1);
    i++;
    printf("text1 i=%d\n",i);
    pthread_mutex_unlock(&lock2);
  }
  
}
void * text2(void * arg)
{
    while(i<1000)
    {
     pthread_mutex_lock(&lock2);
     i++;
    printf("text2 i=%d\n",i);
    pthread_mutex_unlock(&lock1);
    }
  
}
           
Linux系統程式設計之線程深度詳解(有執行個體)

可以看到資料沒有異常吧。簡單了解就是線上程中:

上鎖
     代碼
解鎖
           

之間的代碼必須執行完,這個線程才會被挂起,cpu轉去幹别的事情。

線程的互斥鎖之條件變量

當一個條件沒有滿足會阻塞程式,滿足條件夠可以解除阻塞
           
建立方式:
       靜态方式:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
       
       動态方式:int pthread_cond_init(pthread_cond_t *restrict
        cond,const pthread_condattr_t *restrict attr);
    
條件阻塞:int pthread_cond_wait(pthread_cond_t *restrict 
cond, pthread_mutex_t *restrict mutex);

條件解除阻塞: int pthread_cond_signal(pthread_cond_t *cond);
銷毀:int pthread_cond_destroy(pthread_cond_t *cond);
           
#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "sys/types.h"
#include "signal.h"
#include "time.h"
void * text1(void * arg);//線程1
void * text2(void * arg);//線程2
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lunk;
int i=0;
int main(void)
{
    pthread_t pthread_id[2];

    pthread_create(&pthread_id[0],NULL,text1,NULL);
    pthread_create(&pthread_id[1],NULL,text2,NULL);

    pthread_join(pthread_id[0],NULL);
    pthread_join(pthread_id[1],NULL);

    return 0;
}
void * text1(void * arg)
{

 for(i=0;i<15;i++)
 {
     printf(" i=%d\n",i);
    if(i<10)
     {//阻塞
      printf("i<10阻塞\n");
      pthread_cond_wait(&cond,&lunk);
     }
     printf("i>10阻塞解除\n"); 
     
 }

}
void * text2(void * arg)
{
  while(1)
  {   i++;
      sleep(1);
      if(i>10)
      {
          pthread_cond_signal(&cond);
          break;
      }
  }
}



           

下一篇: 程式運作

繼續閱讀