線程:
回顧線程(一):
1.線程間通信問題
? ?線程間共享同一個資源(臨界資源)
? ?互斥:
? ? ? ? 排他性訪問
? ? ? ? linux系統 -- 提供了Posix標準的函數庫?-- 互斥量(互斥鎖)
? ?原子操作:---
? ?機制:
? ? ? ? 加鎖 -- 解鎖
? ? ? ? 鎖 --- 操作系統 --- (實現的機制,需要操作系統來
? ? ? ? | - 加鎖 -- 用戶態 -- 切換到(耗時) -- 內核態 -- 獲得了 -- 內核態 -- 用戶態 -- 解鎖 |
? ?函數:
? ? ? ? pthread_mutex_t mutex;
? ? ? ? pthread_mutex_init();
? ? ? ? pthread_mutex_lock();
? ? ? ? pthread_mutex_trylock();? ? //? 嘗試獲得鎖,若沒獲得則返回非0值。
????????????????
? ? ? ? pthread_mutex_unlock();
? ? ? ? pthread_mutex_destroy();
線程的同步:
? ? ??
????????同步 ==》有 一定先后順序的 對資源的排他性訪問。
? ? ? ? 要同步的原因:互斥鎖可以控制排他訪問但沒有次序。
?? ?
? ? ? ? 信號量 --- 實現線程間的同步.
?? ?
? ? ? ? 來源? 生活 --- 交通信號燈
信號量的分類:
????????1、無名信號量 ==》線程間通信
????????2、有名信號量 ==》進程間通信
同步機制:
? ? ? ? 信號量(個數) --- 反映的是資源的數量
? ? ? ? 考慮的時候,站在使用這的角度考慮
? ? ? ? 站在a的角度考慮。。。。。。
框架:
????????1. 信號量的定義? ? ? ?sem_t ?sem? //造了一類資源
????????2. 信號量的初始化 ? sem_init?
????????3. 信號量的PV操作 (核心) sem_wait()/ sem_post()
????????4. 信號量的銷毀。 ? sem_destroy
??
信號量函數:
1、定義
sem_t 名字;
2、初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
????????功能:
????????????????將已經定義好的信號量賦值。
????????參數:
? ? ? ? ? ? ? ? @sem 要初始化的信號量
? ? ? ? ? ? ? ? @pshared
????????????????????????pshared = 0 ;表示線程間使用信號量(一般填這個)
????????????????????????!=0 ;表示進程間使用信號量
? ? ? ? ? ? ? ? @value:
????????????????????????信號量的初始值,一般無名信號量(一開始的資源的個數)
????????????????????????都是二值信號量,0 1
????????????????????????0 表示紅燈,進程暫停阻塞
????????????????????????1 表示綠燈,進程可以通過執行
????????????????????????也可以是多個,變成計數信號量
????????????????返回值:
????????????????????????成功 0,失敗 -1;
3、PV操作
int sem_wait(sem_t *sem); //p操作
????????功能:
????????????????判斷當前sem信號量是否有資源可用。
????????????????如果sem有資源(==1),則申請該資源,程序繼續運行
????????????????如果sem沒有資源(==0),則線程阻塞等待,一旦有資源
????????????????則自動申請資源并繼續運行程序。
????????????????消耗了這個sem,就沒有了
?????????????注意:sem 申請資源后會自動執行 sem = sem - 1;
????????參數:
? ? ? ? ? ? ? ? @sem 要判斷的信號量資源
????????????????返回值:
????????????????????????成功 0 ,失敗 -1
int sem_post(sem_t *sem); //V操作
????????功能:
????????????????函數可以將指定的sem信號量資源釋放
????????????????并默認執行,sem = sem+1;
????????????????線程在該函數上不會阻塞。
????????????????產生了這個sem就有了,可以由wait(sem)接受去消耗
????????????????參數:
? ? ? ? ? ? ? ? @sem 要釋放資源的信號量
????????????????返回值:
????????????????????????成功 0,失敗 -1;
4、銷毀
int sem_destroy(sem_t *sem);
練習:? ?hello world
#include<stdio.h>
#include<semaphore.h>
#include<errno.h>
#include<pthread.h>
#include<stdlib.h>sem_t sem_h;
sem_t sem_w;void *do_hello(void *arg)
{while(1) {sem_wait(&sem_h);printf("hello ");sem_post(&sem_w);}return NULL;
}void *do_world(void *arg)
{while(1) {sem_wait(&sem_w);printf("world\n");sem_post(&sem_h);}return NULL;}
typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{int i;pthread_t tid[i];threadF_t pFunc[2] = {do_hello,do_world};sem_init(&sem_h,0,1);sem_init(&sem_w,0,0);for(i = 0;i < 2;++i){int ret = pthread_create(&tid[i],NULL,pFunc[i],NULL);if(ret != 0){errno = ret;perror("pthread_create fail");exit(EXIT_FAILURE);}}sem_destroy(&sem_h);sem_destroy(&sem_w);pthread_detach(tid[0]);pthread_detach(tid[1]);printf("---main----exit\n");pthread_exit(NULL);return 0;
}
進程間的通信方式:
三大類:
1.同主機 ? ---- 基于內存的?
? ???????
?古老的通信方式?
? ? ????????????????//管道 ?----?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?無名管道 ?
?? ? ????????????????????????有名管道
? ????????????????? //信號 ?
?? ?
? ????????IPC對象通信(改進)
? ? ?????????????????消息隊列(用的相對少,這里不討論)
? ? ?????????????????共享內存(*) //最高效?
? ? ?????????????????信號量集() //信號量 ?
?
2.? ? ?
????????//不同主機 、多臺主機
????????? socket //網絡部分?
??
????????//同一主機
? ? ? ? 2.1、古老的通信方式
?? ??? ?????????管道:
?? ??? ? ? ????????????????無名管道 ?
?? ??? ? ? ????????????????有名管道 ?
?? ??? ????????????????????信號
? ? ? ? 2.2、IPC對象通信 system v ? ?BSD ? ? suse fedora ? kernel.org
?? ??? ?????????消息隊列(用的相對少,這里不討論)
?? ??? ?????????共享內存(*) //最高效?
?? ??? ?????????信號量集() //信號量 ?
????????//不同主機?
3、socket通信
?? ??? ?網絡通信
?
1、pipe? 無名管道
使用框架:
????????????????創建管道 ==》讀寫管道 ==》關閉管道
1、無名管道 ===》管道的特例 ===>pipe函數
?? ?特性:
?? ?????????1.1 ?親緣關系進程使用
?? ?????????1.2 ?有固定的讀寫端
? ?
流程:
?? ?創建并打開管道: pipe函數
?? ?
函數:
? ? #include <unistd.h>
?? ?int pipe(int pipefd[2]);
?? ?int pipe(int *pipefd);
?? ?int fd[2];
?? ?????????功能:創建并打開一個無名管道
?? ?????????參數:? @pipefd[0] ==>無名管道的固定讀端//0 -- 標準輸入
?? ??? ????????????????? @pipefd[1] ==>無名管道的固定寫端//1 -- 標準輸出?
?? ?????????返回值: 成功 0
?? ??? ??? ?????????????????失敗 -1;
注意事項:
?????????1、無名管道的架設應該在fork之前進行。??
關閉管道: close();
練習:父進程輸入、子進程打印
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>int main(int argc, const char *argv[])
{int fd[2];if(pipe(fd) < 0){perror("pipe fail");return -1;}char buf[100] = {0};int ret = 0;pid_t pid = fork();while(1){if(pid < 0){perror("fork fail");return -1;}else if(pid > 0){close(fd[0]);printf(">");fflush(stdout);ret = read(0,buf,sizeof(buf));buf[ret] = '\0';write(fd[1],buf,strlen(buf) + 1);if(strncmp(buf,"quit",4) == 0){wait(NULL);close(fd[1]);return 0;}}else if(pid == 0){close(fd[1]);ret = read(fd[0],buf,sizeof(buf));printf("date = %s\n",buf);if(strncmp(buf,"quit",4) == 0){close(fd[0]);exit(0);}}}return 0;
}
管道的讀寫規則:
? ?
? ? 1.讀端存在,寫管道
? ? ? ? ?管道空:可以寫數據
? ? ? ? ?管道滿:會造成-->寫阻塞?
?? ? ?
? ? 2.讀端不存在,寫管道
? ? ? ? ?系統會給進程發一個信號SIGPIPE(管道破裂)
? ? 3.寫端存在,讀管道
? ? ? ? ?管道空,讀不到數據,
? ? ? ? ?這時會造成讀操作阻塞
? ? 4.寫端不存在,讀管道?
? ? ? ? ?如果管道中有數據,則讀取這些數據!
? ? ? ? ?如果沒有數據,讀操作不阻塞,立即返回!
2、fifo有名管道
有名管道===》fifo ==》有文件名稱的管道。
?? ??? ??? ??? ??? ? ?????????????????????????????????????????????????文件系統中可見
框架:
?? ?(1).創建有名管道 -- 類似 文件 (管道文件)?
?? ?(2).打開有名管道 -- open?
?? ?(3).讀寫管道 ? ? -- read/write?
?? ?(4).關閉管道 ?==》卸載有名管道 //close??
1、創建:mkfifo? ? ?//創建了一個有名管道
#include <sys/types.h>
#include <sys/stat.h>
?remove();
int mkfifo(const char *pathname, mode_t mode);
????????功能:
? ? ? ????????????????在指定的pathname路徑+名稱下創建一個權限為
? ? ? ????????????????mode的有名管道文件。
????????參數:@pathname要創建的有名管道路徑+名稱
?? ? ?????????????????mode ?8進制文件權限。
????????返回值:? 成功 0
?? ??? ?????????????????失敗 ?-1;
2、打開有名管道 open
注意:該函數使用的時候要注意打開方式,
?? ?因為管道是半雙工模式,所有打開方式直接決定
?? ?當前進程的讀寫方式。
?? ?一般只有如下方式:
?? ?int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定讀端? ? //阻塞,只有雙方以對應的方式打開的時候才會
?? ?int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定寫端? ?
?? ?不能是 O_RDWR 方式打開文件。
?? ?不能有 O_CREAT 選項,因為創建管道有指定的mkfifo函數
?? ?
?? ?有名管道打開:
?? ?注意,
?? ?如果一端是以只讀,或者只寫方式打開的。
?? ?程序會阻塞,
?? ?阻塞在打開操作。
?? ?直到另一端,以只寫或只讀方式打開。
? ? A.c --- 只讀?
?? ?B.c --- 只寫?
練習:實現雙向通信:
? ? ? ? 打開兩個有名通道文件
// a
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>int main(int argc, const char *argv[])
{if (mkfifo("a_2_b",0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if (mkfifo("b_2_a",0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd_w = open("a_2_b",O_WRONLY);if (fd_w< 0){perror("open fail");return -1;}int fd_r = open("b_2_a",O_RDONLY);if (fd_r< 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024] = {0};if (pid > 0){while (1){printf(">");fflush(stdout);fgets(buf,sizeof(buf),stdin);write(fd_w,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){close(fd_w);close(fd_r);}}}else if (pid == 0){while (1){printf("<");int ret = read(fd_r,buf,sizeof(buf));printf("ret = %d: %s\n",ret,buf);}}return 0;
}// b
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>int main(int argc, const char *argv[])
{if (mkfifo("a_2_b",0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if (mkfifo("b_2_a",0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd_r = open("a_2_b",O_RDONLY);if (fd_r< 0){perror("open fail");return -1;}int fd_w = open("b_2_a",O_WRONLY);if (fd_w< 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024] = {0};if (pid > 0){while (1){printf(">");fflush(stdout);fgets(buf,sizeof(buf),stdin);write(fd_w,buf,strlen(buf)+1);}}else if (pid == 0){while (1){printf("<");int ret = read(fd_r,buf,sizeof(buf));printf("ret = %d: %s\n",ret,buf);if (strncmp(buf,"quit",4) == 0){close(fd_w);close(fd_r);}}}return 0;
}