大家好,我是雜燴君。
Hello系列 , 彙總短而實用的内容。
什麼是多線程程式設計?
1、線程和程序的差別
程序是指正在運作的程式,它擁有獨立的記憶體空間和系統資源,不同程序之間的資料不共享。
線程是程序内的執行單元,它與同一程序内的其他線程共享程序的記憶體空間和系統資源。
2、多線程的優勢和應用場景
多線程是一種并發程式設計方式,它的優勢包括:
- 提高程式的響應速度和運作效率(多核CPU下的多線程)
- 充分利用CPU資源,提高系統的使用率
- 支援多個任務并行執行,提高程式的可擴充性和可維護性
Linux下的多線程程式設計
Linux下C語言多線程程式設計依賴于pthread多線程庫。pthread庫是Linux的多線程庫,是POSIX标準線程API的實作,它提供了一種建立和操縱線程的方法,以及一些同步機制,如互斥鎖、條件變量等。
頭檔案:
#include <pthread.h>
編譯連結需要連結連結庫 pthread。
一、線程的基本操作
1、pthread_create
/**
* @brief 建立一個線程
*
* Detailed function description
*
* @param[in] thread: 一個指向線程辨別符的指針,線程調用後,該值被設定為線程ID;pthread_t為unsigned long int
* @param[in] attr: 用來設定線程屬性
* @param[in] start_routine: 線程函數體,線程建立成功後,thread 指向的記憶體單元從該位址開始運作
* @param[in] arg: 傳遞給線程函數體的參數
*
* @return 線程建立成功,則傳回0,失敗則傳回錯誤碼,并且 thread 内容是未定義的
*/
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
例子test.c:建立一個線程,每1s列印一次。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
void *thread_fun(void *arg)
{
s_thread_running = 1;
while (s_thread_running)
{
printf("thread run...\n");
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread_id, NULL); ///< 阻塞等待線程結束
if (ret != 0)
{
printf("pthread_join error!\n");
exit(EXIT_FAILURE);
}
printf("After Thread\n");
exit(EXIT_SUCCESS);
}
編譯、運作:
gcc test.c -o test -lpthread
2、pthread_join
/**
* @brief 等待某個線程結束
*
* Detailed function description: 這是一個線程阻塞函數,調用該函數則等到線程結束才繼續運作
*
* @param[in] thread: 某個線程的ID
* @param[in] retval: 用于擷取線程 start_routine 的傳回值
*
* @return 線程建立成功,則傳回0,失敗則傳回錯誤碼,并且 thread 内容是未定義的
*/
int pthread_join(pthread_t thread, void **retval);
例子test.c:建立一個線程,進行一次加法運算就傳回。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
void *thread_fun(void *arg)
{
static int res = 0;
int a = 1, b = 2;
res = a + b;
sleep(1);
printf("thread run, a + b = %d, addr = %p\n", res, &res);
pthread_exit(&res);
}
int main(void)
{
int ret = 0;
int *retval = NULL;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("pthread_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread_id, (void **)&retval); ///< 阻塞等待線程結束
if (ret != 0)
{
printf("pthread_join error!\n");
exit(EXIT_FAILURE);
}
if (retval != NULL)
{
printf("After Thread, retval = %d, addr = %p\n", (int)*retval, retval);
}
exit(EXIT_SUCCESS);
}
編譯、運作:
3、pthread_exit
/**
* @brief 退出線程
*
* Detailed function description
*
* @param[in] retval: 它指向的資料将作為線程退出時的傳回值
*
* @return void
*/
void pthread_exit(void *retval);
線程将指定函數體中的代碼執行完後自行結束;
線程執行過程中,被同一程序中的其它線程(包括主線程)強制終止;
線程執行過程中,遇到 pthread_exit() 函數結束執行。
例子test.c:建立一個線程,每個1s列印一次,列印超過5次時調用pthread_exit退出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
const static char *thread_exit_str = "thread_exit ok!";
void *thread_fun(void *arg)
{
static int cnt = 0;
s_thread_running = 1;
while (s_thread_running)
{
cnt++;
if (cnt > 5)
{
pthread_exit((void*)thread_exit_str);
}
printf("thread run...\n");
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
void *thread_res = NULL;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread_id, (void**)&thread_res);
if (ret != 0)
{
printf("thread_join error!\n");
exit(EXIT_FAILURE);
}
printf("After Thread, thread_res = %s\n", (char*)thread_res);
exit(EXIT_SUCCESS);
}
編譯、運作:
使用return退出線程與使用pthread_exit退出線程的差別?
return為通用的函數退出操作,pthread_exit專用與線程,既然pthread庫有提供專門的函數,自然用pthread_exit會好些,雖然使用return也可以。
看看return退出線程與使用pthread_exit退出線程的具體差別:退出主線程。使用pthread_exit退出主線程隻會終止目前線程,不會影響程序中其它線程的執行;使用return退出主線程,主線程退出執行很快,所有線程都會退出。
例子:使用pthread_exit退出主線程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
const static char *thread_exit_str = "thread_exit ok!";
void *thread_fun(void *arg)
{
sleep(1);
printf("thread_fun run...\n");
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
void *thread_res = NULL;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
printf("main thread exit\n");
pthread_exit(NULL);
}
編譯、運作:
例子:使用return退出主線程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
const static char *thread_exit_str = "thread_exit ok!";
void *thread_fun(void *arg)
{
sleep(1);
printf("thread_fun run...\n");
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
void *thread_res = NULL;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
printf("main thread exit\n");
return 0;
}
編譯、運作:
4、pthread_self
/**
* @brief 用來擷取目前線程ID
*
* Detailed function description
*
* @param[in] void
*
* @return 傳回線程id
*/
pthread_t pthread_self(void);
例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
const static char *thread_exit_str = "thread_exit ok!";
void *thread_fun(void *arg)
{
static int cnt = 0;
s_thread_running = 1;
while (s_thread_running)
{
cnt++;
if (cnt > 5)
{
pthread_exit((void*)thread_exit_str);
}
printf("thread run(tid = %ld)...\n", pthread_self());
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
void *thread_res = NULL;
pid_t pid = getpid();
printf("pid = %d\n", pid);
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread_id, (void**)&thread_res);
if (ret != 0)
{
printf("thread_join error!\n");
exit(EXIT_FAILURE);
}
printf("After Thread, thread_res = %s\n", (char*)thread_res);
exit(EXIT_SUCCESS);
}
編譯、運作:
5、pthraad_detach
/**
* @brief 分離線程
*
* Detailed function description: 分離線程,線程結束是系統自動回收線程的資源
*
* @param[in] thread: 某個線程的ID
*
* @return 成功時傳回0,失敗傳回其他值
*/
int pthread_detach(pthread_t thread);
pthread_create建立的線程有兩種狀态:joinable(可結合的)和unjoinable(不可結合的/分離的)。預設是joinable 狀态。
一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的,是以以預設的屬性建立線程時,建立的線程時可結合的,我們需要對線程退出時調用pthread_join對線程資源進行回收。隻有當pthread_join函數傳回時,建立的線程才算終止,才能釋放自己占用的系統資源。
一個不可結合的線程,線程結束後會自動釋放占用資源。
因為pthread_join是一個阻塞的操作,而大多數時候主線程并不希望因為調用pthread_join而阻塞,并且大多數情況下不會使用線程函數體的傳回值,是以這時候可以把線程建立為不可結合的/分離的。
把線程建立為不可結合的/分離的有兩種方式:
- 在建立線程之後,使用pthraad_detach分離線程。
- 在建立線程之前,使用pthread_attr_setdetachstate設定線程以不可結合的/分離的狀态建立。
例子:在建立線程之後,使用pthraad_detach分離線程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
void *thread_fun(void *arg)
{
s_thread_running = 1;
while (s_thread_running)
{
printf("child thread run...\n");
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
printf("Before Thread\n");
ret = pthread_create(&s_thread_id, NULL, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_detach(s_thread_id);
if (ret != 0)
{
printf("pthread_detach error!\n");
exit(EXIT_FAILURE);
}
printf("After Thread\n");
while (1)
{
printf("main thread run...\n");
sleep(1);
}
exit(EXIT_SUCCESS);
}
編譯、運作:
pthread_join與pthraad_detach的差別:
- pthread_detach()即主線程與子線程分離,兩者互相不幹涉,子線程結束同時子線程的資源自動回收。
- pthread_join()即是子線程合入主線程,主線程會一直阻塞,直到子線程執行結束,然後回收子線程資源,并繼續執行。
6、pthread_attr_init
/**
* @brief 初始化一個線程對象的屬性
*
* Detailed function description
*
* @param[in] attr: 指向一個線程屬性的指針
*
* @return 成功時傳回0,失敗傳回其他值
*/
int pthread_attr_init(pthread_attr_t *attr);
如果不設定線程屬性,線程則以預設屬性進行建立,預設的屬性值如:
例子:在建立線程之前,使用pthread_attr_setdetachstate設定線程以不可結合的/分離的狀态建立。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread_id;
static unsigned char s_thread_running = 0;
void *thread_fun(void *arg)
{
s_thread_running = 1;
while (s_thread_running)
{
printf("thread run...\n");
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
printf("Before Thread\n");
pthread_attr_t attr;
ret = pthread_attr_init(&attr);
if (ret != 0)
{
printf("pthread_attr_init error!\n");
exit(EXIT_FAILURE);
}
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ///< 線程以分離的狀态建立
ret = pthread_create(&s_thread_id, &attr, thread_fun, NULL);
if (ret != 0)
{
printf("thread_create error!\n");
exit(EXIT_FAILURE);
}
printf("After Thread\n");
pthread_attr_destroy(&attr); ///< 銷毀線程屬性結構
while (1)
{
sleep(1);
}
exit(EXIT_SUCCESS);
}
二、互斥鎖(mutex)的使用
互斥鎖用于保護一些公共資源。一些公共資源有可能會被多個線程共同使用,如果不做資源保護,可能會産生 意想不到的bug。
一個線程,如果需要通路公共資源,需要獲得互斥鎖并對其加鎖,資源在在鎖定過程中,如果其它線程對其進行通路,也需要獲得互斥鎖,如果擷取不到,線程隻能進行阻塞,直到獲得該鎖的線程解鎖。
互斥鎖API:
#include<pthread.h>
///< 建立互斥對象,用指定的初始化屬性初始化互斥對象
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex_attr_t *mutexattr);
///< 加鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
///< 解鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);
///< 加鎖,但是如果對象已經上鎖則傳回EBUSY錯誤代碼而不阻塞
int pthread_mmutex_trylock(pthread_mutex_t *mutex);
///< 析構并釋放mutex相關資源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥鎖有兩種建立方式:
- 靜态建立:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 動态建立:
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread互斥鎖屬性包括:
- PTHREAD_MUTEX_TIMED_NP:這是預設值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程将會形成一個等待隊列,并在解鎖後按優先級獲得鎖。這種政策可以確定資源配置設定的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP:嵌套鎖。允許同一個線程對同一個鎖成功獲得多次,并通過unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競争。
- PTHREAD_MUTEX_ERRORCHECK_NP:檢錯鎖。如果同一個線程請求同一個鎖,則傳回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同,這樣就保證了當不允許多次加鎖時不會出現最簡單情況下的死鎖。
- PTHREAD_MUTEX_ADAPTIVE_NP:适應鎖,動作最簡單的鎖類型,僅等待一小段時間,如果不能獲得鎖就放棄等待
互斥鎖使用形式:
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL); ///< 初始化互斥鎖
pthread_mutex_lock(&mutex); ///< 加鎖
///< 操作公共資源
pthread_mutex_unlock(&mutex); ///< 解鎖
pthread_mutex_destroy(&mutex); ///< 銷毀互斥鎖
例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread1_id;
static pthread_t s_thread2_id;
static unsigned char s_thread1_running = 0;
static unsigned char s_thread2_running = 0;
static pthread_mutex_t s_mutex;
static int s_cnt = 0;
void *thread1_fun(void *arg)
{
printf("[%s]pthread_mutex_lock ------ s_cnt = %d\n", __FUNCTION__, s_cnt);
pthread_mutex_lock(&s_mutex); ///< 加鎖
for (size_t i = 0; i < 100; i++)
{
s_cnt++;
}
printf("[%s]pthread_mutex_unlock ------ s_cnt = %d\n", __FUNCTION__, s_cnt);
pthread_mutex_unlock(&s_mutex); ///< 解鎖
pthread_exit(NULL);
}
void *thread2_fun(void *arg)
{
printf("[%s]pthread_mutex_lock ------ s_cnt = %d\n", __FUNCTION__, s_cnt);
pthread_mutex_lock(&s_mutex); ///< 加鎖
for (size_t i = 0; i < 100; i++)
{
s_cnt++;
}
printf("[%s]pthread_mutex_unlock ------ s_cnt = %d\n", __FUNCTION__, s_cnt);
pthread_mutex_unlock(&s_mutex); ///< 解鎖
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
///< 建立互斥量
ret = pthread_mutex_init(&s_mutex, NULL);
if (ret != 0)
{
printf("pthread_mutex_init error!\n");
exit(EXIT_FAILURE);
}
///< 建立線程1
ret = pthread_create(&s_thread1_id, NULL, thread1_fun, NULL);
if (ret != 0)
{
printf("thread1_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread1_id, NULL); ///< 阻塞等待線程結束
if (ret != 0)
{
printf("pthread1_join error!\n");
exit(EXIT_FAILURE);
}
///< 建立線程2
ret = pthread_create(&s_thread2_id, NULL, thread2_fun, NULL);
if (ret != 0)
{
printf("thread2_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_join(s_thread2_id, NULL); ///< 阻塞等待線程結束
if (ret != 0)
{
printf("pthread2_join error!\n");
exit(EXIT_FAILURE);
}
printf("main thread, s_cnt = %d\n", s_cnt);
ret = pthread_mutex_destroy(&s_mutex);
{
printf("pthread_mutex_destroy error!\n");
exit(EXIT_FAILURE);
}
return 0;
}
編譯、運作:
三、條件變量的使用
條件變量是線上程中以睡眠的方式等待某一條件的發生,是利用線程間共享的全局變量進行同步的一種機制。
條件變量是線程可用的一種同步機制,條件變量給多個線程提供了一個會合的場所,條件變量與互斥量一起使用時,允許線程以無競争的方式等待特定的條件發生。
條件變量API:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
///< 條件變量初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
///< 銷毀條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
///< 等待條件變量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
///< 帶有逾時功能的 等待條件變量
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
///< 通知條件變量,喚醒至少1個等待該條件的線程
int pthread_cond_signal(pthread_cond_t *cond);
///< 通知條件變量,廣播喚醒等待該條件的所有線程
int pthread_cond_broadcast(pthread_cond_t *cond);
假如有兩個線程,線程1依賴于某個變量才能執行相應的操作,而這個變量正好是由線程2來改變的。這種情況下有兩種方案編寫程式:
方案一:線程1輪詢的方式檢測這個變量是否變化,變化則執行相應的操作。
方案二:使用條件變量的方式。線程1等待線程2滿足條件時進行喚醒。
其中,方案一比較浪費CPU資源。
條件變量的例子:建立兩個線程,線程1對全局計數變量cnt從0開始進行自增操作。線程2列印5的倍數,線程1列印其它數。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static pthread_t s_thread1_id;
static pthread_t s_thread2_id;
static unsigned char s_thread1_running = 0;
static unsigned char s_thread2_running = 0;
static pthread_mutex_t s_mutex;
static pthread_cond_t s_cond;
static int s_cnt = 0;
void *thread1_fun(void *arg)
{
s_thread1_running = 1;
while (s_thread1_running)
{
pthread_mutex_lock(&s_mutex); ///< 加鎖
s_cnt++;
pthread_mutex_unlock(&s_mutex); ///< 解鎖
if (s_cnt % 5 == 0)
{
pthread_cond_signal(&s_cond); ///< 喚醒其它等待該條件的線程
}
else
{
printf("[%s]s_cnt = %d\n", __FUNCTION__, s_cnt);
}
usleep(100 * 1000);
}
pthread_exit(NULL);
}
void *thread2_fun(void *arg)
{
s_thread2_running = 1;
while (s_thread2_running)
{
pthread_mutex_lock(&s_mutex); ///< 加鎖
while (s_cnt % 5 != 0)
{
pthread_cond_wait(&s_cond, &s_mutex); ///< 等待條件變量
}
printf("[%s]s_cnt = %d\n", __FUNCTION__, s_cnt);
pthread_mutex_unlock(&s_mutex); ///< 解鎖
usleep(200 * 1000);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
///< 建立互斥量
ret = pthread_mutex_init(&s_mutex, NULL);
if (ret != 0)
{
printf("pthread_mutex_init error!\n");
exit(EXIT_FAILURE);
}
///< 建立條件變量
ret = pthread_cond_init(&s_cond, NULL);
if (ret != 0)
{
printf("pthread_cond_init error!\n");
exit(EXIT_FAILURE);
}
///< 建立線程1
ret = pthread_create(&s_thread1_id, NULL, thread1_fun, NULL);
if (ret != 0)
{
printf("thread1_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_detach(s_thread1_id);
if (ret != 0)
{
printf("s_thread1_id error!\n");
exit(EXIT_FAILURE);
}
///< 建立線程2
ret = pthread_create(&s_thread2_id, NULL, thread2_fun, NULL);
if (ret != 0)
{
printf("thread2_create error!\n");
exit(EXIT_FAILURE);
}
ret = pthread_detach(s_thread2_id);
if (ret != 0)
{
printf("s_thread2_id error!\n");
exit(EXIT_FAILURE);
}
while (1)
{
sleep(1);
}
return 0;
}
編譯、運作:
以上就是本次的分享,如果覺得文章有用,歡迎收藏、轉發!
相關資料:
- 《UNIX環境進階程式設計(第三版)》
- 《高品質嵌入式Linux C程式設計》
- https://zhuge.blog.csdn.net/article/details/123783374
- https://blog.csdn.net/jinking01/article/details/112185115
如果文章對你有幫助,麻煩幫忙點贊、收藏、轉發,謝謝!
私信回複【嵌入式書籍】,可擷取部落客精心整理的嵌入式電子書一份