【C/C++】C++基礎_3_進程與信號,進程間通信與信號量,/多線程,線程同步


1.進程:fork(),grep 空

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
getpid庫函數功能是獲取進程編號,該函數沒有參數,返回值是進程的編號,pid_t就是typedef int pid_t
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
多進程
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
進程應用,併發的概念如下
在這裏插入圖片描述
併發的應用,以下在服務端中,在CTcpServer類中增加兩個成員函數
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

2.信號:signal(.,EXIT)

如下讓程序後臺運行的兩種方法,這樣ctrl+c無法中止,用killall book1,或kill 進程號
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
如下是信號類型,信號名實質上就是宏
在這裏插入圖片描述
如上子進程結束會向父進程發送信號名爲SIGCHLD,如果父進程沒有處理這個信號,那這個子進程就變成了殭屍進程。信號值11就是自己非法使用空指針或者亂用地址,和信號值9一樣就算加上忽略代碼也不能被忽略。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
下面 EXIT函數就是自定義函數,TcpServer設爲全局變量因爲EXIT函數要訪問它並關閉socket
在這裏插入圖片描述
如下ctrl+c和kill/killall命令都能調用EXIT函數來關閉進程,不叫殺進程了,叫通知退出
在這裏插入圖片描述
在這裏插入圖片描述
下面是操作系統kill命令發信號,不是C語言kill函數。kill不能殺其他用戶進程
在這裏插入圖片描述
kill函數的返回值爲0成功,-1失敗。這個特點會用在進程的監控上,下面argv[1]就是kill第一個參數即pid編號,argv[2]第二個參數是信號值
在這裏插入圖片描述
在這裏插入圖片描述

3.進程間通信:shmget()

進程數據空間是相互獨立的,不能互相訪問。但某些情況下進程間需要互相通信來完成系統某項功能或交換數據,如下是進程通信場景
在這裏插入圖片描述
消息隊列類似socket,不過只能在本機。一般共享內存和信號燈結合起來用,如下是進程通信方式
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
示例程序:對指針操作相當於對共享內存操作
在這裏插入圖片描述
寫入的就是字符串"本程序的進程…",有redis就不用自己寫共享內存
在這裏插入圖片描述
如下是兩個共享內存的操作命令
在這裏插入圖片描述

4.信號量:semget()

信號量(信號燈)本質上是一個計數器,用於協調多個進程(包括但不限於父子進程)對共享數據對象的讀/寫。它不以傳送數據爲目的,主要是用來保護共享資源(信號量、消息隊列、socket連接等),保證共享資源在一個時刻只有一個進程獨享。信號量是一個特殊的變量,只允許進程對它進行等待信號和發送信號操作。最簡單的信號量是取值0和1的二元信號量,這是信號量最常見的形式。通用信號量(可以取多個正整數值)和信號量集方面的知識比較複雜,應用場景也比較少。本文只介紹二元信號量。
Linux中提供了一組函數用於操作信號量,程序中需要包含以下頭文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

在這裏插入圖片描述
在這裏插入圖片描述
爲了便於理解,我把信號量的操作封裝成CSEM類,稱之爲信號燈,類似互斥鎖,包括初始化信號燈、等待信號燈、掛出信號燈和銷燬信號燈。

// book259.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
class CSEM
{
private:
  union semun  // 用於信號燈操作的共同體。
  {
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
  };
 
  int  sem_id;  // 信號燈描述符。
public:
  bool init(key_t key); // 如果信號燈已存在,獲取信號燈;如果信號燈不存在,則創建信號燈並初始化。
  bool wait();          // 等待信號燈掛出。
  bool post();          // 掛出信號燈。
  bool destroy();       // 銷燬信號燈。
};
 
int main(int argc, char *argv[])
{
   CSEM sem;
 
   // 初始信號燈。
   if (sem.init(0x5000)==false) { printf("sem.init failed.\n"); return -1; }
   printf("sem.init ok\n");
  
   // 等待信信號掛出,等待成功後,將持有鎖。
   if (sem.wait()==false) { printf("sem.wait failed.\n"); return -1; }
   printf("sem.wait ok\n");
 
   sleep(50);  // 在sleep的過程中,運行其它的book259程序將等待鎖。
  
   // 掛出信號燈,釋放鎖。
   if (sem.post()==false) { printf("sem.post failed.\n"); return -1; }
   printf("sem.post ok\n");
  
   // 銷燬信號燈。
   // if (sem.destroy()==false) { printf("sem.destroy failed.\n"); return -1; }
   // printf("sem.destroy ok\n");
}
 
bool CSEM::init(key_t key)
{
  // 獲取信號燈。
  if ( (sem_id=semget(key,1,0640)) == -1)
  {
    // 如果信號燈不存在,創建它。
    if (errno==2)
    {
      if ( (sem_id=semget(key,1,0640|IPC_CREAT)) == -1) { perror("init 1 semget()"); return false; }
 
      // 信號燈創建成功後,還需要把它初始化成可用的狀態。
      union semun sem_union;
      sem_union.val = 1;
      if (semctl(sem_id,0,SETVAL,sem_union) <  0) { perror("init semctl()"); return false; }
    }
    else
    { perror("init 2 semget()"); return false; }
  }
 
  return true;
}
 
bool CSEM::destroy()
{
  if (semctl(sem_id,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }
 
  return true;
}
 
bool CSEM::wait()
{
  struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = -1;
  sem_b.sem_flg = SEM_UNDO;
  if (semop(sem_id, &sem_b, 1) == -1) { perror("wait semop()"); return false; }
   
  return true;
}
 
bool CSEM::post()
{
  struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = 1;  
  sem_b.sem_flg = SEM_UNDO;
  if (semop(sem_id, &sem_b, 1) == -1) { perror("post semop()"); return false; }
 
  return true;
}

在這裏插入圖片描述
在這裏插入圖片描述

5.多線程:pthread_create()

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
新客戶端連上啓動一個線程而不是進程,下面爲查看線程主函數怎麼寫,pthread_create是C語言庫函數,yum install -y man-pages
在這裏插入圖片描述
如下在如上一行輸出的頁面EXAMPLE中找到
在這裏插入圖片描述
將thread_start改爲pth_main(線程主函數),如下就按照上面形式這麼寫,pth_main函數作爲pthread_create函數的第三個參數,pthread_create第四個參數是pth_main函數的參數,pth_main函數只有一個無類型指針參數arg,以下在服務端中
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
下面打印出socket
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
如上(void*)ii = void*arg,ii傳入arg
在這裏插入圖片描述
單進程多線程:只殺進程(不存在殺線程),進程殺了線程自動退出。多進程:父進程被殺,子進程不退。多線程:會出現賦值沒完成線程切換,值被取走,值就不是原來那個值。線程有joinable和unjoinable兩種狀態,如果線程是joinable狀態,當線程主函數終止時(自己退出或調用pthread_exit退出)不會釋放線程所佔用內存資源和其它資源,這種線程被稱爲“殭屍線程”。創建線程時默認是非分離的,或者稱爲可連接的(joinable)。避免殭屍線程就是如何正確的回收線程資源,有四種方法:
方法一:創建線程前,調用pthread_attr_setdetachstate將線程設爲detached,這樣線程退出時,系統自動回收線程資源。

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);  // 設置線程的屬性。
pthread_create(&pthid,&attr,pth_main,(void*)((long)TcpServer.m_clientfd);

方法二:創建線程後,在創建線程的程序中調用pthread_detach將新創建的線程設置爲detached狀態。

pthread_detach(pthid);

方法三:創建線程後,在創建線程的程序中調用pthread_join等待線程退出,一般不會採用這種方法,因爲pthread_join會發生阻塞。

pthread_join(pthid,NULL);

方法四:在線程主函數中調用pthread_detach改變自己的狀態。

 pthread_detach(pthread_self());

查看線程:1)在top命令中,如果加上-H參數,top中的每一行顯示的不是進程,而是一個線程。

top -H

2)在ps命令中加-xH參數也可以顯示線程,加grep可以過濾內容。

ps -xH
ps -xH|grep book260

6.線程同步:pthread_mutex_t

在這裏插入圖片描述
在這裏插入圖片描述
pthread_mutex_t 結構體。數據輸入:網頁登陸。數據展示:java,微軟的.net,c#。共享資源:全局變量,全局對象(數據庫連接池,socket,日誌文件對象)。多線程可以共享資源(變量和對象),對編程帶來了方便,但是某些對象雖然可以共享,但在同一個時間只能由一個線程使用,多個線程同時使用會產生衝突,例如socket連接,數據庫連接池。
在這裏插入圖片描述
打開方式爲a+,因爲日誌文件不斷在末尾追加內容
在這裏插入圖片描述
如下book242沒有把內容寫在屏幕上而是寫到了日誌文件
在這裏插入圖片描述
killall book242 殺死進程後上面查看.log文件就可顯示,以下修改爲可選緩衝區寫解決緩衝問題
在這裏插入圖片描述
在這裏插入圖片描述
多個線程用logfile共享資源,可能會出問題(文件內容寫錯亂),下面給logfile定義一個鎖,一個共享資源一個鎖
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
上面運行和之前寫入日誌一樣,表面看不出效果,以下修改睡一秒
在這裏插入圖片描述
如下將前幾章節socket客戶端程序改爲多線程book263.cpp

#include "_public.h"
#include <pthread.h> 
//xx pthread_mutex_t mutex; // 申明一個互斥鎖 
// 與客戶端通信線程的主函數
void *pth_main(void *arg)
{
  int pno=(long)arg;   // 線程編號 
  pthread_detach(pthread_self()); 
  char strbuffer[1024];
 
  for (int ii=0;ii<3;ii++)    // 與服務端進行3次交互。
  {
    //xx pthread_mutex_lock(&mutex);  // 加鎖
    memset(strbuffer,0,sizeof(strbuffer));
    sprintf(strbuffer,"線程%d:這是第%d個超級女生,編號%03d。",pno,ii+1,ii+1);
    if (TcpClient.Send(strbuffer,strlen(strbuffer))<=0) break;
    printf("發送:%s\n",strbuffer);
 
    memset(strbuffer,0,sizeof(strbuffer));
    if (TcpClient.Recv(strbuffer,sizeof(strbuffer))<=0) break;
    printf("線程%d接收:%s\n",pno,strbuffer);
    //xx pthread_mutex_unlock(&mutex);  // 釋放鎖
    // usleep(100);   // usleep(100),否則其它的線程無法獲得鎖。
  }
 
  pthread_exit(0);
}
 
int main()
{
  // 向服務器發起連接請求
  if (TcpClient.ConnectToServer("172.16.0.15",5051)==false)
  { printf("TcpClient.ConnectToServer(\"172.16.0.15\",5051) failed,exit...\n"); return -1; }
 
  //xx pthread_mutex_init(&mutex,0); // 創建鎖
 
  pthread_t pthid1,pthid2;
  pthread_create(&pthid1,NULL,pth_main,(void*)1);   // 創建第一個線程
  pthread_create(&pthid2,NULL,pth_main,(void*)2);   // 創建第二個線程
 
  pthread_join(pthid1,NULL);    // 等待線程1退出。
  pthread_join(pthid2,NULL);    // 等待線程2退出。
 
  //xx pthread_mutex_destroy(&mutex[ii]);   // 銷燬鎖
}

客戶端成功連上服務器後,創建兩個線程,同時與服務端進行通信,發送3個請求報文並接收服務端的迴應。book263.cpp暫時不啓用鎖,先試試效果。啓動服務端程序book261,然後再啓動book263。
在這裏插入圖片描述
發現客戶端的兩個線程的報文收發出現了混亂。把book263.cpp的線程鎖代碼啓用,編譯運行。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章