文章目錄
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的線程鎖代碼啓用,編譯運行。