日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

操作系统实验报告10:线程1

發布時間:2024/6/3 windows 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 操作系统实验报告10:线程1 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

操作系統實驗報告10

實驗內容

  • 實驗內容:線程(1)。
    • 編譯運行課件 Lecture13 例程代碼:
      • Algorithms 13-1 ~ 13-8

實驗環境

  • 架構:Intel x86_64 (虛擬機)
  • 操作系統:Ubuntu 20.04
  • 匯編器:gas (GNU Assembler) in AT&T mode
  • 編譯器:gcc

技術日志

POSIX Pthreads

實驗內容原理:

Pthreads是POSIX標準(IEEE 1003.1c)的擴展線程庫,它定義了一個用于線程創建和同步的API,可以為用戶級庫或內核級庫提供支持,Pthreads是線程行為的規范,操作系統設計人員可以按照他們希望的任何方式執行實現這些規范。許多系統都實現了Pthreads規范,比如UNIX類型的系統,包括Linux, Mac OS X和Solaris,Pthreads中常用的函數有:

函數功能描述
pthread_create創建一個新線程
pthread_exit終止一個線程
pthread_join等待特定的線程退出
pthread_yield釋放CPU從而讓其它線程可以運行
pthread_attr_init創建和初始化一個線程的屬性結構
pthread_attr_destroy清除一個線程的屬性結構

其中實驗中用到的函數有:

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void*),void *arg);

pthread_create()函數用來創建一個線程,其中第一個參數為指向線程標識符的的一個指針,第二個參數為線程屬性,第三個參數為線程運行的函數的起始地址,第四個參數為向運行函數傳遞的參數。

int pthread_join(pthread_t thread, void **retval);

pthread_join()函數用來等待一個線程結束,其中第一個參數為等待退出的進程的進程號,第二個參數為退出的線程的返回值。

void pthread_exit(void *retval)

pthread_exit()函數用來退出線程,其中參數代表線程的返回值。

int pthread_attr_init(pthread_attr_t *attr)

pthread_attr_init()函數用來初始化一個線程屬性對象,其中參數代表線程屬性結構體指針變量

  • 驗證實驗alg.13-1-pthread-create.c

執行程序命令:

gcc alg.13-1-pthread-create.c -pthread ./a.out 10 ./a.out 100 ./a.out -10 ./a.out asd

分析:

可以看到,向程序傳遞一個大于0的參數,程序創建一個線程進行計算后會返回從1到這個參數的所有整數的和,最后輸出結果,如果輸入的是一個小于等于0或者非數字的參數,那么程序返回結果就是0。

實現細節解釋:

一開始使用語句pthread_create(&ptid, &attr, &runner, argv[1])創建一個線程,其中參數&ptid為指向線程標識符的的一個指針,參數&attr為要設置的線程屬性,參數&runner為線程運行的函數的起始地址,參數argv[1]為向運行函數傳遞的參數,在線程運行函數中:

static void *runner(void *param) {int i, upper;upper = atoi(param);sum = 0;for (i =1; i <= upper; i++)sum += i;pthread_exit(0); }

參數param指向的是傳遞的參數argv[1],sum是全局變量,函數的作用是對1到參數之間的所有正整數進行求和并把結果保存在全局變量sum里,最后使用語句pthread_exit(0)返回值為0。

回到主函數中,在創建了線程之后,又使用函數pthread_join(ptid, NULL),其中參數ptid即為剛才創建的線程的標識符,NULL為默認屬性,函數的作用是使主線程等待這個計算求和值的線程運行完后再運行,不然還沒得到計算結果主線程就繼續向下執行,有可能出錯。

最后打印求和值sum。

  • 驗證實驗alg.13-1-pthread-create-1-1.c

執行程序命令:

gcc alg.13-1-pthread-create-1-1.c -pthread ./a.out 10 ./a.out 100 ./a.out -10 ./a.out asd

分析:

相比之前一個程序alg.13-1-pthread-create,這個程序還打印了計算線程的返回值16

實現細節解釋:

與之前一個程序alg.13-1-pthread-create相比,這個程序的計算線程函數中:

static void *runner(void *param) {int i, upper;upper = atoi(param);sum = 0;for ( i = 1; i <= upper; i++)sum += i;int *retptr = (int *)malloc(sizeof(int));*retptr = 16;pthread_exit((void *)retptr); }

在線程空間中申請了一塊動態內存給一個整型指針,這個整型指針指向整數16,最后使用語句pthread_exit((void *)retptr)將這個指針作為線程的返回值,利用指針實現了向主線程傳遞值。

在主函數中,語句pthread_join(ptid, (void **)&retptr)通過整型指針retptr接收到了計算線程的返回值,并把這個指針指向的值16打印出來,最后釋放指針的動態內存。

  • 驗證實驗alg.13-1-pthread-create-1-2.c

執行程序命令:

gcc alg.13-1-pthread-create-1-2.c -pthread ./a.out 10 ./a.out 100 ./a.out -10 ./a.out asd

分析:

相比之前一個程序alg.13-1-pthread-create-1-1,這個程序打印的計算線程的返回值是求和的值

實現細節解釋:

與之前一個程序alg.13-1-pthread-create-1-1相比,這個程序的計算線程函數中:

static void *runner(void *param) {int i, upper;upper = atoi(param);sum = 0;for (i = 1; i <= upper; i++)sum += i;pthread_exit((void *)&sum); }

線程中的sum是一個未初始化的全局變量,位于bss段,最后使用的語句pthread_exit((void *)&sum)返回的是一個在線程的bss段的一個地址,里面是求和值

在主函數中,語句pthread_join(ptid, (void **)&retptr)通過整型指針retptr接收到了計算線程的返回值,并把這個指針指向的sum的值打印出來。

  • 驗證實驗alg.13-1-pthread-create-1-3.c

執行程序命令:

gcc alg.13-1-pthread-create-1-3.c -pthread ./a.out

分析:

相比之前一個程序alg.13-1-pthread-create-1-2,這個程序打印的計算線程的返回值是從1到計算線程中的臨時變量upper即10之間所有正整數求和的值

實現細節解釋:

與之前一個程序alg.13-1-pthread-create-1-2相比,這個程序的計算線程函數中:

static void *runner(void *param) {int *sum = (int *)param;int upper = 10;int i;*sum = 0;for (i = 1; i <= upper; i++)*sum += i;pthread_exit((void *)sum); }

線程中的sum是線程中的一個臨時變量,位于棧段,最后使用的語句pthread_exit((void *)sum)返回的是一個在線程的棧段的一個指針,指向的是求和值

在主函數中,語句pthread_join(ptid, (void **)&retptr)通過整型指針retptr接收到了計算線程的返回值,并把這個指針指向的sum的值打印出來。

  • 驗證實驗alg.13-1-pthread-create-2.c

執行程序命令:

gcc alg.13-1-pthread-create-2.c -pthread ./a.out 10 ./a.out 100 ./a.out -10 ./a.out asd

分析:

相比之前的程序alg.13-1-pthread-create-1-2,這個程序打印的計算線程的返回值是一個字符串,為Hello, world!

實現細節解釋:

與之前的程序alg.13-1-pthread-create-1-2相比,這個程序的計算線程函數中:

static void *runner(void *param) {int i = 1;int upper = atoi(param);sum = 0;for (; i <= upper; i++)sum += i;char msg[] = "Hello, world!";char *retptr = (char *)malloc((strlen(msg)+1)*sizeof(char)); /* allocated in process space */strcpy(retptr, msg);pthread_exit((void *)retptr); }

在線程空間中申請了一塊動態內存給一個字符型指針,這個字符串為Hello, world!,最后使用語句pthread_exit((void *)retptr)將這個字符串作為線程的返回值,利用指針向主線程傳遞值。

在主函數中,語句pthread_join(ptid, (void **)&retptr)通過整型指針retptr接收到了計算線程的返回值,并把這個指針的字符串Hello, world!打印出來,最后釋放指針的動態內存。

  • 驗證實驗alg.13-1-pthread-create-3.c

執行程序命令:

gcc alg.13-1-pthread-create-3.c -pthread ./a.out 5

分析:

可以看到,向程序傳遞的參數為要創建的線程數,沒有傳遞參數則創建的線程數默認為5,主函數會根據傳遞的參數的值創建相應的進程數,但是由于向創建的線程中傳遞的值是容易被主線程改變的值i,所以創建的線程中獲得的值很難預測,會出現混亂,造成程序錯誤

實現細節解釋:

一開始使用語句pthread_create(&ptid[i], NULL, ftn, (void *)&i)在一個for循環中創建與傳遞參數的值對應的多個線程,在線程運行函數中:

static void *ftn(void *arg) {int *numptr = (int *)arg;int num = *numptr;char *retval = (char *)malloc(80*sizeof(char));sprintf(retval, "This is thread-%d, ptid = %lu", num, pthread_self( ));printf("%s\n", retval);pthread_exit((void *)retval); }

參數arg指向的是傳遞的參數i,線程會打印語句這是第i個線程,ptid為當前線程的線程號,并返回打印的語句的字符串

回到主函數中,在創建了線程之后,又在一個for循環中使用函數pthread_join(ptid[i], (void **)&retptr),函數的作用是使主線程等待被創建的線程運行完后再運行,不然創建的線程還沒返回主線程就繼續向下執行,有可能出錯,打印是第幾個線程以及線程返回的語句。

  • 驗證實驗alg.13-1-pthread-create-3-1.c

執行程序命令:

gcc alg.13-1-pthread-create-3-1.c -pthread ./a.out 5

分析:

相比之前的程序alg.13-1-pthread-create-3,這個程序創建的線程按照順序獲取了傳遞的值,打印的語句的順序和編號沒有發生混亂,實現正常輸出。

實現細節解釋:

與之前的程序alg.13-1-pthread-create-3相比,這個程序的創建線程的for循環中,最后多了一個sleep(1)

for (i = 0; i < max_num; i++) {ret = pthread_create(&ptid[i], NULL, ftn, (void *)&i);if(ret != 0) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(1);}sleep(1); }

每創建一個線程主線程就休眠1s,這樣傳遞進每個線程的值i發生混亂的概率變小,最后打印出的結果每個線程的編號和順序都保持正常。

  • 驗證實驗alg.13-1-pthread-create-4.c

執行程序命令:

gcc alg.13-1-pthread-create-4.c -pthread ./a.out 5

分析:

相比之前的程序alg.13-1-pthread-create-3-1,這個程序并沒有再每次創建線程時讓主線程休眠1s,而是一開始:

int thread_num[max_num]; for (i = 0; i < max_num; i++) {thread_num[i] = i; }

然后使用語句pthread_create(&ptid[i], NULL, ftn, (void *)&thread_num[i])在for循環中創建進程,最后創建的線程也按照順序獲取了傳遞的值,打印的語句的順序和編號沒有發生混亂,實現正常輸出。

實現細節解釋:

因為一開始使用了其它的內存放置了變化的i值,所以thread_num數組中的值后面并不會被主線程改變,傳遞進每個線程中的是一個穩定的值,最后打印出的結果每個線程的編號和順序都保持正常。

  • 驗證實驗alg.13-2-pthread-shm.c

執行程序命令:

gcc alg.13-2-pthread-shm.c -pthread ./a.out

分析:

這是一個程序開始后,打印當前進程的pid,ppid的值和休眠秒數secnd的值,然后休眠secnd秒后,再繼續執行,打印出sleeper wakes up and returns的簡單程序。

實現細節解釋:

程序一開始將三條信息message 1 by parent、message 2 by parent和message 3 by parent分別存入了msg.msg1、msg.msg2和msg.msg3,并打印parent說了這三條信息。

然后使用語句pthread_create(&tid1, &attr, &runner1, (void *)&msg)、pthread_create(&tid2, &attr, &runner2, (void *)&msg) != 0)分別創建兩個線程,兩個函數的作用分別是將字符串message 1 changed by child1和message 2 changed by child2復制進msg.msg1、msg.msg2,然后使用pthread_join(tid1, NULL)和pthread_join(tid2, NULL)使主線程等待兩個線程復制完字符串后再執行,最后打印結果,發現確實復制成功。

  • 驗證實驗alg.13-3-pthread-stack.c

執行程序命令:

gcc alg.13-3-pthread-stack.c -pthread ./a.out

分析:

每次創建的線程執行后,最后都會再次遞歸,再次使用線程函數test,第0到4次使用test函數會被打印出來,最后又打印了4次遞歸,然后遞歸超過了棧的大小,發生了段錯誤,程序結束。

5142884096-19650321024 = 94330880,94330880/1965032 = 48(字節), 說明每次迭代的系統開銷大概是48字節。

實現細節解釋:

程序一開始動態申請了一塊大小為STACK_SIZE的內存給字符型指針stackptr,初始化線程后,使用語句pthread_attr_setstack(&tattr, stackptr, STACK_SIZE)設置線程棧的大小和地址,再使用語句pthread_create(&ptid, &tattr, &test, NULL)創建線程,開始遞歸,直到遞歸超過了設置的線程棧的大小,程序結束。

OpenMP

OpenMP是一組編譯器指令和編程用的API,支持C、C++或FORTRAN編程,它提供了對共享內存環境中并行編程的支持。

OpenMP將并行區域標識為可以并行運行的代碼塊。

應用程序開發人員在并行區域向代碼中插入編譯器指令,這些指令指示OpenMP庫運行時并行執行該區域。

當OpenMP遇到指令

#pragma omp parallel

它創建的線程數量與系統中處理內核的數量相同(例如,對于Intel CPU,每個內核有兩個線程)。所有線程同時執行并行區域,當每個線程退出并行區域時,它將終止。

如果使用指令

#pragma omp parallel num_threads(i)

那么可以指定創建的線程數量,將創建i個線程執行并行區域。

  • 驗證實驗alg.13-4-openmp-demo.c

執行程序命令:

gcc alg.13-4-openmp-demo.c -fopenmp ./a.out

分析:

程序對于每條打印語句創建了不同的線程數,分別打印各條語句,第一條語句被打印了2次,第二條語句被打印了2次,第三條語句被打印了4次,第四條語句被打印了6次。

從程序的不同線程的tid也可以看到,有時候線程會被復用。

實現細節解釋:

對于第一條語句,使用指令#pragma omp parallel,默認創建的線程數為2,那么就有兩個線程打印了第一條語句,第一條語句被打印的次數為2次,如果使用指令#pragma omp parallel num_threads(i),那么會創建i個線程,來執行代碼塊中的語句,比如第二條語句為2,第三條語句為4,第四條語句為6,分別被打印了2次、4次、6次。

  • 驗證實驗alg.13-5-openmp-matrixadd.c

執行程序命令:

gcc alg.13-5-openmp-matrixadd.c -fopenmp ./a.out 100 ./a.out 500 ./a.out 1000 ./a.out 5000 ./a.out 6000

分析:

可以看到,使用兩個線程比不使用多線程進行矩陣加法計算速度要快,運行時間更短,說明使用多線程進行并行計算可以提高計算效率,但是使用四個線程比使用兩個線程計算時間長,這是因為只有兩個核,線程數量比核的數量多時,線程會被頻繁切換,這樣需要的時間就會變更長,反而會降低計算效率和速度。

實現細節解釋:

向程序傳遞的參數表示要計算的是幾行幾列的矩陣加法,程序分別不使用omp創建多線程,使用omp創建2個線程,創建4個線程執行矩陣加法,并記錄時間進行運行時間的比較。

多線程編程中使用fork()函數

實驗內容原理:

  • fork()系統調用用于創建一個單獨的、重復的進程。但是fork()和exec()系統調用的語義在多線程程序中會發生變化:
    • 如果程序中有一個線程調用fork(),那么新進程可能:
      • 復制所有線程
      • 只復制調用fork()系統調用的線程(在Ubuntu中)
        • 這會造成很高的風險
    • 一些UNIX系統有兩個版本的fork()
  • exec()系統調用的工作方式通常是,如果線程調用exec()系統調用,則exec()的參數中指定的程序將替換調用進程,包括其所有線程。
    • 如果在fork()之后立即調用exec(),fork()的進程只需要復制調用線程。
      • 不需要復制所有線程,因為exec()的參數中指定的程序將替換調用進程
    • 否則,fork的進程在fork之后不會調用exec(),它應該復制調用進程的所有線程

一個建議是,盡量避免在多線程編程中使用fork()函數

  • 驗證實驗alg.13-6-fork-pthread-demo1.c

執行程序命令:

gcc alg.13-6-fork-pthread-demo1.c -pthread ./a.out

分析:

可以看到,在與a.out有關的子進程中的進程中,pid=22828, spid=22162的進程為父進程,pid=22829, spid=22162的為父進程中在創建子進程之前創建的線程,pid=22830, spid=22828的進程為創建的子進程,說明子進程也復制了父進程的線程

程序一直在打印0,這是由父進程創建的線程所引起的。

實現細節解釋:

一開始,程序使用語句pthread_create(&ptid, NULL, &thread_worker, NULL)創建了一個線程,線程函數為:

static void *thread_worker(void *args) {while (1) {printf("%d\n", i);sleep(1);}pthread_exit(0); }

作用為不停地打印0,這個線程處在父進程中

然后,主函數使用語句pid_t pid = fork()創建了一個子進程,在子進程中,將變量i設為1,打印語句in child,然后系統調用ps -l -T查看父進程,父進程創建的線程,子進程的信息,最后退出。

在父進程中,使用wait(&pid)等待子進程結束后,打印語句in parent,然后系統調用ps -l -T查看父進程,父進程創建的線程,子進程的信息,最后while (1)使父進程一直進行,那么父進程之前所創建的線程也會一直進行。

  • 驗證實驗alg.13-7-fork-pthread-demo2.c

執行程序命令:

gcc alg.13-7-fork-pthread-demo2.c -pthread ./a.out

分析:

可以看到,在與a.out有關的子進程中的進程中,pid=23020, spid=22822的進程為父進程,pid=23021, spid=22822的為父進程創建的線程,pid=23022, spid=23020的進程為線程中創建的子進程,說明在線程中創建的子進程復制了作為其父進程的線程和主線程,子進程將其父進程的線程當作了主線程,這會引發一些不可預知的后果

程序一直在交替地打印0和1,打印0是由創建的線程所引起的,打印1是由創建的線程創建的子進程所引起的。

實現細節解釋:

一開始,程序使用語句pthread_create(&ptid, NULL, &thread_worker, NULL)創建了一個線程,線程函數為:

static void *thread_worker(void *args) {pid_t pid = fork();if(pid < 0 ) {return (void *)EXIT_FAILURE;}if(pid == 0) { /* child pro */i = 1;printf("in thread_worker's forked child\n");system("ps -l -T | grep a.out");}sleep(2);while (1) {printf("%d\n", i); sleep(2);}pthread_exit(0); }

在這個線程函數中,可以看到,使用語句pid_t pid = fork()在線程中創建了一個進程,在子進程中,設置變量i為1,打印語句in thread_worker's forked child,然后系統調用ps -l -T | grep a.out查看與a.out有關的進程的信息,然后sleep(2)休眠2s,接著不停每隔2s打印一次i的值1。

在父進程中,首先sleep(2)休眠2s,接著不停每隔2s打印一次i的值0。

回到主線程中,首先sleep(2)休眠2s,打印語句in start main(),然后系統調用ps -l -T | grep a.out查看與a.out有關的進程的信息,最后while (1)使父進程一直進行,那么父進程之前所創建的線程也會一直進行。

如果在主線程中添加一條語句return 1:

int main(void) {pthread_t ptid;pthread_create(&ptid, NULL, &thread_worker, NULL);sleep(2) ;printf("in start main()\n");system("ps -l -T | grep a.out");return 1;while (1) ;pthread_join(ptid, NULL);return EXIT_SUCCESS; }

執行命令:

gcc alg.13-7-fork-pthread-demo2.c -pthread ./a.out pkill -f a.out

分析:

可以看到,pid=23057, spid=22882的為父進程中的主線程,pid=23058, spid=22882的為父進程創建的線程,pid=23059, spid=23057的進程為線程中創建的子進程,說明在線程中創建的子進程復制了作為其父進程的線程和主線程,子進程將其父進程的線程當作了主線程,這會引發一些不可預知的后果

程序不停地打印1,即使按下ctrl+c也無法停止程序,使用ps查看發現pid為23059的線程中創建的子進程仍在執行,只能使用指令pkill -f a.out才能停止程序。

實現細節解釋:

在主線程中加上了語句return 1之后,主線程還未等創建的線程及其創建的子進程結束就直接結束了,這會造成一些不可預知的后果。

信號處理

實驗內容原理:

  • UNIX系統中使用一個信號來通知進程某個特定事件已經發生:

    • 信號可以同步或異步接收
  • 所有信號應遵循以下模式:

    • 特定事件的發生會產生一個信號。
    • 信號被傳送到進程。
    • 信號一旦發出,就必須進行處理。
  • 信號由這兩個信號處理程序之一處理

    • 內核運行的默認處理程序
    • 可以重寫默認處理程序的用戶定義處理程序。
  • 對于單線程,一個信號傳遞給一個進程。

  • 驗證實驗alg.13-8-sigaction-demo.c

執行程序命令:

gcc alg.13-8-sigaction-demo.c ./a.out

執行截圖:

分析:

程序開始后,準備捕捉信號Ctrl+c,當在終端輸入Ctrl+c后,程序捕捉到,進入處理程序,在這段處理程序中,Ctrl+\暫時被屏蔽,信號沒用,休眠10s后,處理程序完成,重新取消屏蔽,繼續準備捕捉信號Ctrl+c,如果輸入的是Ctrl+\,可以直接導致core dumped錯誤,程序結束。

實現細節解釋:

首先定義一個struct sigaction類型的變量newact,struct sigaction類型是一個與檢查或修改與指定信號相關聯的處理動作相關的結構體。

然后語句newact.sa_handler = my_handler,表示設置處理信號函數為用戶自定義的my_handler()函數。

接著使用sigemptyset(&newact.sa_mask)將newact的信號集初始化為空,使用sigaddset(&newact.sa_mask, SIGQUIT)將信號編號為3的SIGQUIT(Ctrl+\)添加到信號集中,newact的參數sa_flags設置為默認值0

然后打印語句now start catching Ctrl+c,使用函數sigaction(SIGINT, &newact, NULL)將信號編號為2的SIGINT(Ctrl+c)指定新的信號處理方式newact,并進行記錄。

最后while (1)使進程一直進行。

總結

以上是生活随笔為你收集整理的操作系统实验报告10:线程1的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。