C語言多線程
多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,兩種類型的多任務處理:基於進程和基於線程。
- 基於進程的多任務處理是程序的並發執行。
- 基於線程的多任務處理是同一程序的片段的並發執行。
多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱為一個線程,每個線程定義了一個單獨的執行路徑。
本教程假設您使用的是 Linux 操作系統,我們要使用 POSIX 編寫多線程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多種類 Unix POSIX 系統上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。
創建線程
下面的程序,我們可以用它來創建一個POSIX 線程:
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
在這里,pthread_create 創建一個新的線程,並讓它可執行。下面是關於參數的說明:
| 參數 | 描述 |
|---|---|
| thread | 指向線程標識符指針。 |
| attr | 一個不透明的屬性對象,可以被用來設置線程屬性。您可以指定線程屬性對象,也可以使用默認值 NULL。 |
| start_routine | 線程運行函數起始地址,一旦線程被創建就會執行。 |
| arg | 運行函數的參數。它必須通過把引用作為指針強制轉換為 void 類型進行傳遞。如果沒有傳遞參數,則使用 NULL。 |
創建線程成功時,函數返回 0,若返回值不為 0 則說明創建線程失敗。
終止線程
使用下面的程序,我們可以用它來終止一個 POSIX 線程:
#include <pthread.h>
pthread_exit (status)
在這里,pthread_exit 用於顯式地退出一個線程。通常情況下,pthread_exit() 函數是在線程完成工作后無需繼續存在時被調用。
如果 main() 是在它所創建的線程之前結束,並通過 pthread_exit() 退出,那么其他線程將繼續執行。否則,它們將在 main() 結束時自動被終止。
連接和分離線程
我們可以使用以下兩個函數來連接或分離線程:
pthread_join (threadid, status)
pthread_detach (threadid)
pthread_join() 子程序阻礙調用程序,直到指定的 threadid 線程終止為止。當創建一個線程時,它的某個屬性會定義它是否是可連接的(joinable)或可分離的(detached)。只有創建時定義為可連接的線程才可以被連接。如果線程創建時被定義為可分離的,則它永遠也不能被連。pthread_join() 函數來等待線程的完成。
注意
pthread庫不是Linux系統默認的庫,連接時需要使用庫libpthread.a, 在使用pthread_create創建線程時,在編譯中要加-lpthread參數:
gcc createThread.c -lpthread -o createThread.o
./createThread.
Test 1 無參數傳遞的線程並發編程實例
// 基於線程的並發編程
// Test_1 createThread
#include <stdio.h>
#include <pthread.h>
/*
* pthread庫不是Linux系統默認的庫,連接時需要使用庫libpthread.a,
* 在使用pthread_create創建線程時,在編譯中要加-lpthread參數:
* gcc createThread.c -lpthread -o createThread.o
* ./createThread.o
* 加上上面兩句以后編譯成功,並輸出結果
* */
#define NUM_Threads 5
// 線程的運行函數
void *PrintHello(void *arg)
{
printf("Hello,World of Thread in C!\n");
return 0;
}
int main()
{
int i;
int ret;
// 定義線程的id變量,多個變量使用數組
pthread_t tids[NUM_Threads];
for (i=0; i<NUM_Threads; i++)
{
// 參數依次是: 創建的線程id,線程參數,調用的函數,傳入的函數參數
ret = pthread_create(&tids[i], NULL, PrintHello, NULL);
if (ret != 0)
{
printf("pthread_create error: error_code = \n");
}
}
// 等各個線程推出后,進程才結束
pthread_exit(NULL);
return 0;
}
/*
* 在CLion(Ubuntu)中輸出結果為
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
* */
Test 2 簡單參數傳遞的線程並發編程實例
// 基於線程的並發編程,向線程傳遞參數1
// Test_2_createThread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_Threads 5
// 線程的運行函數
void *PrintHelloId(void *threadid)
{
// 對傳入的參數進行強制類型轉換,由無類型指針變為整形指針,然后再讀取
int tid = *((int *)threadid);
printf("Hello,World, Thread %d\n",tid);
return 0;
}
int main()
{
pthread_t pthreads[NUM_Threads];
int i, rc;
// 用數組存儲i的數值
int indexes[NUM_Threads];
for (i=0; i<NUM_Threads; i++)
{
printf("main() : 創建線程 %d \n",i);
indexes[i] = i; // 保存i的數值
// 在indexes傳入參數的時候必須轉換為無類型指針
rc = pthread_create(&pthreads[i], NULL, PrintHelloId, (void *)&indexes[i]);
if (0 != rc)
{
printf("Error: 無法創建線程!\n");
exit(-1);
}
}
pthread_exit(NULL);
return 0;
}
/*
* 在CLion(Ubuntu)中輸出結果是
main() : 創建線程 0
main() : 創建線程 1
Hello,World, Thread 0
main() : 創建線程 2
Hello,World, Thread 1
main() : 創建線程 3
Hello,World, Thread 2
main() : 創建線程 4
Hello,World, Thread 3
Hello,World, Thread 4
* */
Test 3 結構體參數傳遞的線程並發編程實例
// 基於線程的並發編程,向線程傳遞參數2(傳遞結構體)
// Test_3_createThread
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_Threads 5
typedef struct thread_data{
int threadid;
char message;
}THDATA,*PTHDATA;
void * PrintHello(void * pthreadid)
{
PTHDATA tid = (PTHDATA)pthreadid;
printf("This is Pthread : %d ;info : %c \n",tid->threadid, tid->message);
return 0;
}
int main(void)
{
pthread_t Pthread[NUM_Threads];
THDATA index[NUM_Threads];
int i, ret;
for (i = 0; i < NUM_Threads; i++)
{
printf("main() : 創建線程 %d \n",i);
index[i].threadid = i;
index[i].message = 'A'+i%10;
ret = pthread_create(&Pthread[i], NULL, PrintHello, (void *)&index[i]);
if (0 != ret)
{
printf("Error: 創建線程失敗!\n");
exit(-1);
}
}
pthread_exit(NULL);
return 0;
}
/*
* 在CLion(Ubuntu)中輸出結果是
main() : 創建線程 0
main() : 創建線程 1
This is Pthread : 0 ;info : A
main() : 創建線程 2
main() : 創建線程 3
This is Pthread : 2 ;info : C
main() : 創建線程 4
This is Pthread : 3 ;info : D
This is Pthread : 4 ;info : E
This is Pthread : 1 ;info : B
* */
Test 4 線程的連接編程實例
// 基於線程的並發編程,連接或分離線程
// Test_4_createThread
// 2019年10月27日14:45:11 尚未完全搞明白
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_Pthread 5
void *PrintHello(void * pthreadid)
{
int tid = *((int *)pthreadid);
printf("Sleeping in thread %d ,...exiting \n",tid);
return 0;
}
int main(void)
{
int i, ret;
pthread_t Pthread[NUM_Pthread];
pthread_attr_t attr; // 定義線程屬性
void * status;
int index[NUM_Pthread];
// 初始化並設置線程為可連接
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (i=0; i<NUM_Pthread; i++)
{
printf("main() : 創建線程 %d \n",i);
index[i] = i;
ret = pthread_create(&Pthread[i], NULL, PrintHello, (void *)&index[i]);
}
// 刪除屬性,並等待其他線程
pthread_attr_destroy(&attr);
for (i=0; i<NUM_Pthread; i++)
{
ret = pthread_join(Pthread[i], status);
if (0 != ret)
{
printf("Error: unable to join,%d\n",ret);
exit(-1);
}
printf("main(): complete thread id : %d",i);
printf(" exiting with status : %p\n",status);
}
printf("main() : program exiting.\n");
pthread_exit(NULL);
return 0;
}
信號量機制
Test 5 信號量同步進行寫入
// 用信號量進行同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#define Len 100 // 設置輸入內容長度
sem_t bin_sem;
char work_area[Len]; // 存放輸入內容
void *Thread_func(void *arg)
{
// 等待信號量有大於0的值然后退出
sem_wait(&bin_sem);
while (0 != strncmp("end", work_area, 3))
{
printf("Input %ld characters\n", strlen(work_area)-1);
}
return 0;
}
int main(void)
{
int res; // 存放命令的返回值
pthread_t Pthread; // 創建線程
void *thread_result; // 存放線程處理結果
// 初始化信號量,並設置初始值為0
res = sem_init(&bin_sem, 0, 0);
if (0 != res)
{
perror("Semaphore initialization failes");
exit(EXIT_FAILURE);
}
// 創建新線程 0
res = pthread_create(&Pthread, NULL, Thread_func, NULL);
if (0 != res)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Enter 'end' to finish\n");
// 當工作區內不是以end開頭的字符串,則繼續輸入
while (0 != strncmp("end", work_area, 3))
{
// 以標准輸入獲取輸入到工作區內
fgets(work_area, Len, stdin);
sem_post(&bin_sem); // 信號量+1
}
printf("\n Waiting for thread to finish...\n");
// 等待線程結束
res = pthread_join(Pthread, &thread_result);
if (0 != res)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
sem_destroy(&bin_sem); // 銷毀信號量
exit(EXIT_SUCCESS);
return 0;
}
Test 6 互斥信號量實現對臨界資源操作
// 用互斥信號量進行同步
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#define Len 3 // 自增計算次數
#define NUM_Pthread 5 // 設置線程的長度
int count = 1; // 在數據段共享資源,多個進程搶占臨界資源
// 對於臨界資源,應該添加互斥鎖
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *Thread_func(void *threadid)
{
int tid = *((int *)threadid);
int i, val;
printf("Pthread ID : %d \n",tid);
for (i=0; i<NUM_Pthread; i++)
{
pthread_mutex_lock(&mutex);
val = count;
printf("val = %d \n",val++);
count = val;
pthread_mutex_unlock(&mutex);
}
return 0;
}
int main(void)
{
int res; // 存放命令的返回值
int i;
pthread_t Pthread[NUM_Pthread]; // 創建線程
int index[NUM_Pthread];
for (i=0; i<NUM_Pthread; i++)
{
index[i] = i;
// 創建線程
res = pthread_create(&Pthread[i], NULL, Thread_func, (void *)&index[i]);
if (0 != res)
{
printf("Error: 創建線程失敗!\n");
exit(-1);
}
}
for (i=0; i<NUM_Pthread; i++)
{
// 匯合線程
pthread_join(Pthread[i], NULL);
}
printf("count = %d\n",count);
pthread_exit(NULL);
return 0;
}
// 在運行此程序無互斥鎖時,我們不僅得到錯誤的答案,而且每次得到的答案都不相同
// 分析
// 當多個對等線程在一個處理器上並發運行時,機器指令以某種順序完成,每個並發執行定義了線程中指令的某種順序
參考博文:
- [1] C++ 多線程
- [2] 信號量和互斥量C語言示例理解線程同步
- [3] 線程同步,條件變量,mutex鎖,信號量
