線程基礎
:什麼是多線程
計算機有線程
計算機: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
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SN4QTOzMGZlZ2Y3ATO4MGOxYzXxUzNwETMxEzLcFTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL1M3Lc9CX6MHc0RHaiojIsJye.png)
可以看到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
函數功能:取消一個正在運作的線程
函數原型: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");
}
運作結果:
可以看到在第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;
}
線程的互斥鎖
舉個不太恰當的例子:
互斥鎖
什麼是互斥
隻能有一方存在
要麼你存在
要麼我存在
要麼你用
要麼我用
什麼是鎖
大門鎖
測試需要維修
維修人員把門鎖了
進去維修了
這段時間你能進去上廁所?
隻有當維修人員搞完了
解鎖完畢
你才能去上廁所
你上廁所的時候希不希望别去跟你
一起在廁所上廁所
你一般會把門鎖上
那麼互斥鎖有什麼用呢?
互斥鎖就是保證每次隻有一個操作在運作。舉個例子。
#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去了,可能會出現一些問題?
上面的代碼你執行一下會發現:
這個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);
}
}
可以看到資料沒有異常吧。簡單了解就是線上程中:
上鎖
代碼
解鎖
之間的代碼必須執行完,這個線程才會被挂起,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;
}
}
}