您好,登錄后才能下訂單哦!
上次說到解決并發(fā)的問題,需要用到信號量。下面,簡單復(fù)習(xí)一下。
信號量是一種變量,它只能取正整數(shù)值,對這些正整數(shù)只能進行兩種操作:等待和信號。(在我的理解,信號量就是用來訪問一些臨界資源而設(shè)計的)
用兩種記號來表示信號量的這兩種操作:
P(semaphore variable) 代表等待(請求資源)
V(semaphore variable) 代表信號(釋放資源)
可以取多個正整數(shù)值的信號量叫做“通用信號量”
假設(shè)我們有一個信號量變量sv,則pv操作的定義如下
P(sv):如果sv的值大于零,就給它減去1;如果sv的值等于零,就掛起該進程的執(zhí)行
V(sv): 如果有其他進程因等待sv變量而被掛起,就讓它恢復(fù)執(zhí)行;如果沒有進程因等待sv變量而被掛起,就給它加1
需要用到的函數(shù)如下:
#include < sys/types.h>
#include < sys/ipc.h>
#include < sys/sem.h>
int semget(key_t key, int nsems, int semflg);
作用:創(chuàng)建一個新的信號量或者取得一個現(xiàn)有的信號量的關(guān)鍵字
key: 是一個整數(shù)值,不相關(guān)的進程將通過這個值去訪問同一個 信號量
num_sems:需要使用的信號量個數(shù),它幾乎總是取值為1
sem_flags:是一組標(biāo)志,其作用與open函數(shù)的各種標(biāo)志很相似,它低端的九個位是該信號量的權(quán)限,其作用相當(dāng)于文件的訪問權(quán)限,可以與鍵值IPC_CREATE做按位的OR操作以創(chuàng)建一個新的信號量
成功時將返回一個正數(shù)值,它就是其他信號量函數(shù)要用到的那個標(biāo)識碼,如果失敗,將返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
作用:改變信號量的鍵值(就是用來執(zhí)行PV操作的)
semid:是該信號量的標(biāo)識碼,也就是semget函數(shù)的返回值
sops:是個指向一個結(jié)構(gòu)數(shù)值的指針
nsop:進行操作信號量的個數(shù),即sops結(jié)構(gòu)變量的個數(shù),需大于或等于1。最常見設(shè)置此值等于1,只完成對一個信號量的操作
Semop調(diào)用的一切動作都是一次性完成的,這是為了避免出現(xiàn)因使用了多個信號量而可能發(fā)生的競爭現(xiàn)象
其中,sembuf的結(jié)構(gòu)體如下:
sem_num是信號量的編號,如果你的工作不需要使用一組信號量,這個值一般就取為0。
sem_op是信號量一次PV操作時加減的數(shù)值,一般只會用到兩個值,一個是“-1”,也就是P操作,等待信號量變得可用;另一個是“+1”,也就是我們的V操作,發(fā)出信號量已經(jīng)變得可用
sem_flag通常被設(shè)置為SEM_UNDO.她將使操作系統(tǒng)跟蹤當(dāng)前進程對該信號量的修改情況
int semctl(int semid, int semnum, int cmd, ...);
sem_id: 是由semget函數(shù)返回的一個信號量標(biāo)識碼
sem_num: 信號量的編號,如果在工作中需要使用到成組的信號量,就要用到這個編號;它一般取值為0,表示這是第一個也是唯一的信號量
comman:將要采取的操作動作
如果還有第四個參數(shù),那它將是一個“union semun”復(fù)合結(jié)構(gòu).
先說一下cmd,有以下幾個:
其中,比較常用的是以下兩個:
IPC_RMID:刪除一個已經(jīng)沒有人繼續(xù)使用的信號量標(biāo)識碼
再說一下第四個參數(shù),這個函數(shù)比較特別,當(dāng)有四個參數(shù)時,是一個“union semun”復(fù)合結(jié)構(gòu).(頭文件里可能沒有,最好自己重新定義一下)
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
做個小練習(xí),租賃汽車?yán)印?br/>共5個廠,每個廠分別有汽車數(shù):3 7 5 0 6??紤]租車問題(訪問臨界資源)
factory.c:
#include < stdio.h>
#include < sys/types.h>
#include < sys/ipc.h>
#include < sys/sem.h>
/*
租賃汽車?yán)?5個廠,每個廠分別有汽車數(shù):3 7 5 0 6
=>信號量個數(shù) = 5 , 信號量的值分別為:3 7 5 0 6 ,信號量編號從0開始
*/
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int main()
{
int i;
int sem_id;
int values[5] = {3,7,5,0,6};
union semun value = {0};
//創(chuàng)建信號量
sem_id = semget(1000,5,0);
if(sem_id != -1)
{
for(i = 0 ;i < 5;i++)
{
semctl(sem_id , i , IPC_RMID);
}
}
sem_id = semget(1000,5,IPC_CREAT);
printf("%d\n",sem_id);
if(sem_id == -1)
{
perror("producer semget");
return -1;
}
//設(shè)置信號量
for(i = 0 ; i < 5 ; i++)
{
printf("%d ",semctl(sem_id,i,GETVAL));
}
printf("\n");
for(i = 0 ;i < 5; i++)
{
value.val = values[i];
//打印信號量
semctl(sem_id,i,SETVAL,value.val);
}
for(i = 0 ; i < 5 ; i++)
{
printf("%d ",semctl(sem_id,i,GETVAL));
}
return 0;
}
client.c:
#include < stdio.h>
#include < sys/types.h>
#include < sys/ipc.h>
#include < sys/sem.h>
/*
struct sembuf
{
unsigned short sem_num; // semaphore number
short sem_op; // semaphore operation
short sem_flg; // operation flags
Flags recognized in sem_flg are
IPC_NOWAIT and SEM_UNDO.
*/
int main()
{
int sem_id;
struct sembuf buf = {0};
//打開信號量
sem_id = semget(1000,5,0);
if(sem_id == -1)
{
perror("consumer semget");
return -1;
}
printf("waiting...\n");
//借車
buf.sem_num = 0; //信號量編號
buf.sem_op = -1; //請求資源
buf.sem_flg = SEM_UNDO; //自動釋放
semop(sem_id,&buf,1); //第三個參數(shù)必須是1
printf("get a car.\n");
//正在借
sleep(20);
//還車
buf.sem_op = +1; //釋放資源
semop(sem_id,&buf,1);
printf("back a car.\n");
return 0;
}
運行結(jié)果:
當(dāng)執(zhí)行第三個客戶端的時候,會一直等待,知道第一輛車還了之后。
學(xué)習(xí)信號量最經(jīng)典的幾個問題,都可以拿來練練。生產(chǎn)者消費者問題、讀者寫者問題、哲學(xué)家進餐問題等都是一樣的。
要求:產(chǎn)品數(shù)量初始為0,生產(chǎn)者負(fù)責(zé)生產(chǎn),即+1,消費者負(fù)責(zé)消費,即-1。倉庫最多只能存放10個產(chǎn)品
下面給出生產(chǎn)者消費者問題的代碼:
(我使用的是兩個信號量,通常課本上會使用一個信號量,可以自己試試)
public.c:
#ifndef _PUBLIC_H_
#define _PUBLIC_H_
#include < stdio.h>
#include < sys/types.h>
#include < sys/ipc.h>
#include < sys/sem.h>
unsigned short values[2] = {10,0};
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
#endif
生產(chǎn)者
#include " public.h"
//信號量:控制進程間同步問題,共同的資源,資源的數(shù)量(信號量的值)>=0。
//P等待/請求 -1,V信號/釋放,+1
//semget(創(chuàng)建、打開)\semctl(刪除、初始化)、semop(PV)
int main()
{
int semid;
//兩個信號量,一個生產(chǎn),初始化10,一個消費,初始化為0
//生產(chǎn)了一件商品后,生產(chǎn)者(第零個信號量-1),消費者(第一個信號量+1)
//unsigned short values[2] = {10,0};
union semun sem = {0};
struct sembuf buf1 = {0};
struct sembuf buf2 = {0};
//1.創(chuàng)建信號量
semid = semget(1000,0,0);
if(semid == -1)
{
semid = semget(1000,2,IPC_CREAT);
if(semid == -1)
{
perror("semget open.");
return 1;
}
}
//2.信號量初始化
sem.array = values;
semctl(semid,0,SETALL,sem);
//3.pv操作
while(1)
{
//生產(chǎn),信號量[0]減1 buf1
buf1.sem_num = 0;
buf1.sem_op = -1;
buf1.sem_flg = SEM_UNDO;
// printf("can produce?\n");
semop(semid,&buf1,1);//最后一個參數(shù),只要>0就行
// printf("yes.\n");
sleep(1);
//生產(chǎn)完一件產(chǎn)品,信號量[1]加1 buf2
buf2.sem_num = 1;
buf2.sem_op = +1;
buf2.sem_flg = SEM_UNDO;
semop(semid,&buf2,1);
printf("finish produce.now: %d\n",values[1]);
// printf("all product: %d\n",sem.array[1]);
}
return 0;
}
消費者
#include " public.h"
int main()
{
int semid;
struct sembuf buf1 = {0};
struct sembuf buf2 = {0};
semid = semget(1000,0,0);
if(semid == -1)
{
perror("semget open");
return 1;
}
//負(fù)責(zé)消費
while(1)
{
//能否消費 信號量[1] buf2
buf2.sem_num = 1;
buf2.sem_op = -1;
buf2.sem_flg = SEM_UNDO;
semop(semid,&buf2,1);
//消費
sleep(3);
//信號量[0] buf1
buf1.sem_num = 0;
buf1.sem_op = +1;
buf1.sem_flg = SEM_UNDO;
semop(semid,&buf1,1);
printf("consume one product.\n");
}
}
運行結(jié)果:
先運行./producer。 當(dāng)生產(chǎn)到10個產(chǎn)品之后,就會阻塞。此時運行./consumer,每3s取走一個產(chǎn)品,1s后生產(chǎn)者會生產(chǎn)一個產(chǎn)品。
現(xiàn)在,可以繼續(xù)我們的小程序了。有了以上兩個練習(xí)之后,就更容易理解了。跟生產(chǎn)者消費者問題很類似。
我們?yōu)樽x寫設(shè)置兩個信號量,一個控制讀,一個控制寫。寫信號量初始化為1,寫完后-1,變成0。讀信號量初始化為0,寫操作后,變成在線用戶數(shù)-1。如果所有用戶都讀完了,讀信號量為0,寫信號量為1。
public.h:
#ifndef _PUBLIC_H_
#define _PUBLIC_H_
#include < stdio.h>
#include < string.h>
#include < sys/types.h>
#include < sys/ipc.h>
#include < sys/msg.h>
#include < sys/shm.h>
#include < signal.h>
#include < sys/sem.h>
#include < string>
#include < map>
#include < iostream>
using namespace std;
//用戶信息結(jié)構(gòu)體
typedef struct user_t
{
pid_t pid;
char uname[10]; //后面加上用戶名不重名、密碼驗證
}USER_T;
//登錄消息隊列結(jié)構(gòu)體
typedef struct login_msg_t
{
long type;
USER_T user;
}LMSG_T;
//聊天消息結(jié)構(gòu)體
typedef struct msg_t
{
USER_T user;
char acMsg[100];
}MSG_T;
//消息隊列:用戶登錄
#define LOGIN_TYPE 1
#define EXIT_TYPE 2
#define MSG_KEY 1000
#define MSG_SIZE sizeof(LMSG_T)-sizeof(long)
//共享內(nèi)存:用戶列表(空閑塊:0-空閑,1-占用)
#define SHM_USER_KEY 1001
#define MAX_USER 100
#define SHM_USER_SIZE MAX_USER + MAX_USER * sizeof(USER_T)
//共享內(nèi)存:聊天內(nèi)容
#define SHM_MSG_KEY 1002
#define SHM_MSG_SIZE sizeof(MSG_T)
//信號:更新用戶列表,讀消息
#define SIGNAL_USERS 34
#define SIGNAL_CHAT 35
//讀寫信號量
#define SEM_KEY 1003
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//兩個信號量,一個控制讀,一個控制寫
//寫信號量初始化為1,寫完后-1,變成0
//讀信號量初始化為0,寫操作后,變成在線用戶數(shù)-1
//如果所有用戶都讀完了,讀信號量為0,寫信號量為1
union semun sem = {0};
struct sembuf buf1 = {0}; //寫
struct sembuf buf2 = {0}; //讀
#endif
server.cpp:
#include " public.h"
int main()
{
int msg_id;
int shm_id;
LMSG_T loginMsg = {0};
char *userAddr;
USER_T *pUser; //用戶真正寫入的地址
map<int,string> userMap; //用戶列表
map<int,string>::iterator it;
int i;
/*1、創(chuàng)建消息隊列:用戶登錄*/
msg_id = msgget(MSG_KEY,0);
if(msg_id == -1)
{
msg_id = msgget(MSG_KEY,IPC_CREAT);
if (msg_id == -1)
{
perror("server msgget");
return -1;
}
}
/*2、創(chuàng)建共享內(nèi)存:用戶列表*/
shm_id = shmget(SHM_USER_KEY,0,0);
if (shm_id != -1)
{//已經(jīng)存在,刪除
shmctl(shm_id,IPC_RMID,NULL);
}
shm_id = shmget(SHM_USER_KEY,SHM_USER_SIZE,IPC_CREAT);
userAddr = (char *)shmat(shm_id,NULL,0);//映射
pUser = (USER_T *)(userAddr + MAX_USER);
memset(userAddr,0,SHM_USER_SIZE);//初始化
/*3、創(chuàng)建共享內(nèi)存:聊天信息*/
int shm_msg_id = shmget(SHM_MSG_KEY,0,0);
if (shm_msg_id != -1)
{
shmctl(shm_msg_id,IPC_RMID,NULL);
}
shm_msg_id = shmget(SHM_MSG_KEY,SHM_MSG_SIZE,IPC_CREAT);
char *msgAddr = (char *)shmat(shm_msg_id,NULL,0);
memset(msgAddr,0,SHM_MSG_SIZE);
/*4、創(chuàng)建信號量*/
int sem_id;
sem_id = semget(SEM_KEY,0,0);
if (sem_id != -1)
{
semctl(sem_id,0,IPC_RMID);
semctl(sem_id,1,IPC_RMID);
}
sem_id = semget(SEM_KEY,2,IPC_CREAT);
//初始化信號量的值
sem.val = 1;
semctl(sem_id,0,SETVAL,sem); //寫
sem.val = 0;
semctl(sem_id,1,SETVAL,sem); //讀
//一直監(jiān)聽,是否有用戶上線
while (1)
{
memset(&loginMsg,0,sizeof(LMSG_T));
msgrcv(msg_id,&loginMsg,MSG_SIZE,0,0); //任何消息都接收
switch(loginMsg.type)
{
case LOGIN_TYPE:
//登錄
cout<<"client "<<loginMsg.user.uname<<":"<<loginMsg.user.pid<<" is logining..."<<endl;
//2.1 將登錄信息寫入共享內(nèi)存(先找到空閑塊)
for (i = 0 ; i < MAX_USER ; i++)
{
if (*(userAddr + i) == 0)
{
//空閑
break;
}
}
if (i < MAX_USER)
{
*(userAddr + i) = 1;
*(pUser + i) = loginMsg.user;
userMap.insert( pair<int,string>(loginMsg.user.pid,loginMsg.user.uname) );
}
else
{
cout<<"online users are full.\n"<<endl;
return 1;
}
//2.2 發(fā)消息通知所有在線用戶
for (it = userMap.begin();it != userMap.end();it++)
{
kill((*it).first,SIGNAL_USERS);
}
break;
case EXIT_TYPE:
//退出
cout<<"client "<<loginMsg.user.uname<<":"<<loginMsg.user.pid<<" is exiting..."<<endl;
for (i = 0 ; i < MAX_USER ; i++)
{
if ((pUser+i)->pid == loginMsg.user.pid)
{
*(userAddr+i) = 0;
break;
}
}
for (it = userMap.begin();it != userMap.end();it++)
{
if ((*it).first == loginMsg.user.pid)
{
continue; //自己退出,不用再通知自己
}
kill((*it).first,SIGNAL_USERS);
}
break;
}
}
return 0;
}
client.cpp:
#include " public.h"
char *userAddr;
USER_T *pUser;
char *msgAddr;
MSG_T *pMsg;
map<int,string> userMap; //用戶列表
map<int,string>::iterator it;
int sem_id;
void PrtUserList(int sig_no)
{
//讀取共享內(nèi)存里的用戶列表數(shù)據(jù)
userMap.clear();
cout<<"==== online users ===="<<endl;
for (int i = 0 ;i < MAX_USER ; i++)
{
if(*(userAddr + i) == 1)
{
cout<<(pUser + i)->uname<<endl;
userMap.insert(pair<int,string>( (pUser+i)->pid, (pUser+i)->uname ));
}
}
cout<<"========= "<<userMap.size()<<" ========="<<endl;
}
void GetChatMsg(int sig_no)
{
//讀取共享內(nèi)存里的聊天內(nèi)容
sleep(10);
MSG_T msg = {0};
msg = *pMsg;
cout<<"receive msg from "<<msg.user.uname<<" : "<<msg.acMsg<<endl;
// 4.3 讀完之后:讀信號量-1,讀到最后一個用戶,讀信號量為0,寫信號量再設(shè)為1
buf2.sem_num = 1;
buf2.sem_op = -1;
buf2.sem_flg = SEM_UNDO;
semop(sem_id,&buf2,1);
if (semctl(sem_id,1,GETVAL) == 0) //讀信號量為0
{
// printf("done.\n");
sem.val = 1;
semctl(sem_id,0,SETVAL,sem);
}
}
int main()
{
char acOrder[20] = "";
int msg_id;
LMSG_T loginMsg = {0};
char uname[10] = "";
int shm_id;
char toWho[10] = ""; //聊天對象
MSG_T msg = {0}; //聊天消息結(jié)構(gòu)體
char acMsg[100] = ""; //聊天內(nèi)容
cout<<"------------onlineChat-------------"<<endl;
cout<<"username:";
cin>>uname;
//2.3 注冊消息(放在最前面)
signal(SIGNAL_USERS,PrtUserList);
signal(SIGNAL_CHAT,GetChatMsg);
/*2、打開用戶列表共享內(nèi)存(要比寫消息隊列早)*/
shm_id = shmget(SHM_USER_KEY,0,0);
if (shm_id == -1)
{
perror("client userlist shmget");
return -1;
}
userAddr = (char*)shmat(shm_id,NULL,0);
pUser = (USER_T*)(userAddr + MAX_USER);
/*3、打開聊天信息共享內(nèi)存*/
int shm_msg_id = shmget(SHM_MSG_KEY,0,0);
if (shm_msg_id == -1)
{
perror("client chatmsg shmget");
return -1;
}
msgAddr = (char *)shmat(shm_msg_id,NULL,0);
pMsg = (MSG_T *)msgAddr;
/*4、打開信號量*/
sem_id = semget(SEM_KEY,0,0);
if (sem_id == -1)
{
perror("client semget");
return -1;
}
/*1、打開消息隊列*/
msg_id = msgget(MSG_KEY,0);
if(msg_id == -1)
{
perror("client msgget");
return -1;
}
//登錄,寫消息隊列
loginMsg.type = LOGIN_TYPE; //設(shè)置登錄的消息類型為1
loginMsg.user.pid = getpid();
memcpy(loginMsg.user.uname,uname,strlen(uname));
cout<<loginMsg.user.uname<<" is logining..."<<endl;
msgsnd(msg_id,&loginMsg,MSG_SIZE,0);
//等待寫
while(1)
{
putchar('#');
fflush(stdout);
scanf("%s",acOrder);
if (strcmp(acOrder,"exit") == 0) //退出
{
cout<<loginMsg.user.uname<<" is exiting..."<<endl;
loginMsg.type = EXIT_TYPE; //設(shè)置退出的消息類型為2
msgsnd(msg_id,&loginMsg,MSG_SIZE,0);
break;
}
else if (strcmp(acOrder,"users") == 0) //查看在線用戶列表
{
kill(getpid(),SIGNAL_USERS);
}
else if (strcmp(acOrder,"chat") == 0) //進入聊天模式
{
cout<<"to who: ";
cin>>toWho;
cout<<"say: ";
memset(acMsg,0,100);
cin>>acMsg;
// 4.1 寫之前:P(等待/請求)操作,寫信號量-1
buf1.sem_num = 0;
buf1.sem_op = -1;
buf1.sem_flg = SEM_UNDO;
semop(sem_id,&buf1,1);
// 3.1 把聊天內(nèi)容寫進共享內(nèi)存
memcpy(msg.acMsg,acMsg,strlen(acMsg));
msg.user = loginMsg.user;
memcpy(msgAddr,&msg,SHM_MSG_SIZE);
if (strcmp(toWho,"all") == 0) //群聊
{
// 4.2 寫之后:設(shè)置讀信號量為在線用戶數(shù)-1
sem.val = userMap.size() - 1;
semctl(sem_id,1,SETVAL,sem);
//通知所有人去讀
for (it = userMap.begin();it != userMap.end();it++)
{
if ((*it).first != getpid())
{
kill((*it).first,SIGNAL_CHAT);
}
}
}
else //私聊
{
for (it = userMap.begin();it != userMap.end();it++)
{
if (strcmp((*it).second.c_str() , toWho) == 0)
{
kill((*it).first,SIGNAL_CHAT);
break;
}
}
}
}
memset(acOrder,0,sizeof(acOrder));
}
//解除映射
shmdt(&userAddr);
shmdt(&msgAddr);
return 0;
}
運行結(jié)果:
運行結(jié)果就不演示了,此時,再sleep(),模擬并發(fā)的情況,就不會出現(xiàn)上次的問題了,畢竟此時的聊天內(nèi)容的共享內(nèi)存已經(jīng)變成臨界資源了,用新號良控制之后,就不會有同時讀寫的問題了。
未完待續(xù)....
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。