IPC: Internal Processes Communication
进程间通信
实质:进程间数据的交换
在一个进程中有一个全局变量a,然后给a赋值一些特殊含义的值
再让另外一个进程去访问这个a,能否实现进程间的通信?
不能,因为进程间的地址空间是独立的
文件可以使进程间发生通信
文件在文件系统中,大家都可以访问的
缺点: 速度太慢,效率太低
在操作系统内核中开辟了一块空间(某种机制),进程去访问
IPC方式:
管道
pipe 无名管道
fifo 有名管道
信号 signal
消息队列 System V 消息队列 / POSIX 消息队列
信号量 System V 信号量 / POSIX 信号量
共享内存 System V 共享内存 / POSIX 共享内存
socket 套接字通信
============================
POSIX / System V
POSIX (Portable Operating System Interface) 可移植操作系统接口
System V (System Five)
POSIX 和 System V 是一种应用于系统的接口协议
POSIX相对于SystemV可以说是更加新的标准,语法相对简单
在Linux编程都是支持 POSIX 和 System V
目录
1、管道 3
3、无名管道 pipe 3
4、fifo有名管道 5
2、信号 6
1、信号 6
2、信号处理的过程 8
3、Linux下与信号相关的API函数 8
3、消息队列 10
1、操作 System V IPC的一下相关命令 10
2、消息队列的操作 11
4、共享内存 16
1、共享内存 16
2、System V 共享内存的API函数 16
5、信号量 22
1、信号量 23
2、如何来保护? 23
3、信号量如何实现? 23
4、信号量的实现 24
4.1)System V semaphore 24
4.2)POSIX semaphore ——> 单个信号量 32
6、线程 39
1、 线程 39
2、线程的概念 39
3、Linux下线程的接口函数 40
4、线程间的同步机制 44
5、生产者-消费者模型 46
6、线程条件变量 46
7、线程池 thread pool 51

1、管道

  1. 以前进程间通信是通过文件<br /> 缺点:速度慢,效率低 <br /> 优点:不需要提供额外的接口函数,直接利用文件系统的函数接口<br /> 问题:<br /> 文件的内容在外部设备中 <br /> 能不能把文件的内容放到内核中<br /> ---》管道:管道文件,内容在内核<br /> pipe 无名管道<br /> fifo 有名管道

3、无名管道 pipe

  1. 1)<br /> 它在文件系统上没有名字(没有inode) ,它的内容在内核中 <br /> 访问pipe的方式是通过文件系统的API <br /> 它不能用open,然后read/write需要文件描述符<br /> 所以创建一个pipe的时候,就会返回文件描述符 <br /> pipe在创建的时候,会在内核开辟一块缓冲区,作为pipe文件内容的存储空间<br /> 同时返回两个文件描述符(一个用来读,一个用来写)<br /> 特点: <br /> 1pipe有两端,一端用来写,一端用来读<br /> 2)按顺序来读,不能使用lseek <br /> 3)内容读走了,就没有了<br /> 4pipe随内核的持续性<br /> 2)接口函数 <br /> pipe <br /> NAME<br /> pipe - create pipe<br /> SYNOPSIS<br /> #include <unistd.h><br /> int pipe(int pipefd[2]);<br /> 功能:用来在内核中创建一个无名管道<br /> 参数: <br /> pipefd:数组 <br /> pipefd[0] 保存读的文件描述符<br /> pipefd[1] 保存写的文件描述符<br /> 返回值: <br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> 注意: <br /> 1pipe本身是一个全双工,但是两个进程用同一个管道去实现全双工通信,<br /> 就必须使用某种机制(方式)去实现同步,不然的话,就可能读到自己写的数据<br /> 一般做成两个或多个管道,一个用于读,一个用于写,人为的当作“半双工”来使用<br /> 2pipe <br /> 能不能任意两个进程都用pipe来进行通信? <br /> 不可以 <br /> 只能用于父子进程吗? <br /> 也不尽然 <br /> pipe只能用于有亲缘关系(两个进程的共同祖先来创建的管道)的进程<br /> 练习:利用无名管道实现父子进程的简单通信 <br /> int main()<br /> {<br /> //先创建无名管道<br /> int pfd[2];<br /> pipe(pfd);<br /> //再创建子进程<br /> pid_t pid = fork();<br /> if(pid > 0) //父进程 write <br /> {<br /> write(pfd[1], "hello son", 10);<br /> int w;<br /> wait(&w);<br /> }<br /> else if(pid == 0) //子进程 read <br /> {<br /> char buf[12] = {0};<br /> int r = read(pfd[0], buf, 10);<br /> if(r > 0)<br /> {<br /> printf("%s\n",buf);<br /> }<br /> }<br /> else <br /> {<br /> perror("fork error ");<br /> return -1;<br /> }<br /> } <br /> pipe无名管道只能用于有亲缘关系(两个进程的共同祖先创建的管道)的进程<br /> pipe没有名字

4、fifo有名管道

  1. fifopipe的基础上,给fifo在文件系统中创建了一个inode(有了一个文件名)<br /> 但是 它的文件内容在内核中,随内核的持续性,文件名在文件系统中,随内核的持续性 <br /> fifopipe一样,除了fifo在文件系统上有一个文件名 <br /> 怎么去创建fifo <br /> mkfifo<br /> NAME<br /> mkfifo - make a FIFO special file (a named pipe)<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/stat.h><br /> int mkfifo(const char *pathname, mode_t mode);<br /> 功能:是用来在文件系统中创建一个fifo <br /> 参数: <br /> pathname:要创建的有名管道的路径名 例如: /home/china/fifo242<br /> mode:创建有名管道的权限 <br /> 有两种方式指定: <br /> 1)宏 S_IRUSR, S_IWUSR, S_IXUSR, ......<br /> 2)八进制 <br /> 0666 <br /> 返回值: <br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> 如果errno == EEXIST 已经存在所导致的创建失败<br /> man fifo <br /> NAME<br /> fifo - first-in first-out special file, named pipe<br /> DESCRIPTION<br /> fifo有名管道,它和pipe无名管道类似,除了在文件系统中有一个名字<br /> 它可以被多个进程打开 用来读或者写 <br /> 当进程用fifo来交换数据的时候,内核没有把数据写入到文件系统,而是保存在内核中<br /> 因此,fifo在文件系统中是没有内容了,它仅仅是作为文件系统的一个入口<br /> 提供一个文件名,给其他进程去访问 <br /> 在数据交换之前,fifo的两端(readwrite)必须都打开<br /> 通常情况下,打开fifo发一端,会阻塞 直到另一端也被打开 <br /> 一个进程也可以“非阻塞”的方式(O_NONBLOCK)打开<br /> 在这种情况下,只读会成功,即使写没有被打开<br /> 只写会失败,且errno == ENXIO ,除非读端已经打开 <br /> 非阻塞 “不等待”<br /> 如果文件没有内容,read不会阻塞,直接返回一个错误码<br /> 如果文件没有空间,write不会阻塞,直接返回一个错误码<br /> 阻塞 “等待”<br /> 如果文件没有内容,read会阻塞(直到有数据或者出错)<br /> 如果文件没有空间,write会阻塞(直到有空间或者出错)<br /> 练习: <br /> 利用fifo有名管道,实现两个进程的通信 <br /> 1.c <br /> #define FIFO_NAME "/home/china/fifo242"<br /> int main()<br /> {<br /> //创建一个有名管道 <br /> int re = mkfifo();<br /> if(re == -1)<br /> {<br /> if(errno != EEXIST) //管道文件不是因为已经存在的而创建失败的<br /> {<br /> perror("mkfifo error ");<br /> return -1;<br /> }<br /> }<br /> //打开fifo <br /> //操作 write <br /> //关闭文件 <br /> }<br /> 2.c <br /> #define FIFO_NAME "/home/china/fifo242"<br /> int main()<br /> {<br /> }

2、信号

1、信号

信号是进程间通信的一种方式,这个方式没有传输数据
只是在内核中传递了一个整数,这个整数就是 信号值
不同的信号值,代表不同的含义
同样的用户可以自定义信号,自定义的信号值和含义由程序设计者来解释
查看信号值: trap -l
man 7 signal
当一个进程收到一个信号值,可能发送以下5种行为
Term 默认行为是终止
Ign 忽略
Core 输出信息,然后再终止
Stop 停止
Cont 如果当前进程是停止,那么就继续该进程

Signal value Action Comment
───────────────────────────────────────SIGHUP 1 Term (终止) 控制终端的挂起操作,或者是控制进程死亡时这个终端使用的进程都会收到SIGHUP
SIGINT 2 Term 键盘上收到中断信号 Ctrl + C
SIGQUIT 3 Core 键盘上的退出信号 Ctrl + Z ,Ctrl + D
SIGILL 4 Core 非法指令 (例如:除数为0)
SIGABRT 6 Core 进程调用abort函数时,就会产生SIGABRT
SIGFPE 8 Core 浮点数运算异常
SIGKILL 9 Term 杀死信号
SIGSEGV 11 Core 非法内存引用 Segmentation fault 段错误
SIGPIPE 13 Term 当你往一个管道写数据时,没有读的进程,就会产生这个信号
SIGALRM 14 Term 当调用alarm函数时,会产生这个信号 (闹钟信号)
SIGTERM 15 Term 终止信号
SIGUSR1 30,10,16 Term 用户自定义信号1
SIGUSR2 31,12,17 Term 用户自定义信号2
SIGCHLD 20,17,18 Ign 子进程退出时,父进程会收到这个信号
SIGCONT 19,18,25 Cont 如果当前进程是停止,那么就继续该进程
SIGSTOP 17,19,23 Stop 停止
SIGTSTP 18,20,14 Stop 由控制终端发起的暂停信号
注意:
The signals SIGKILL and SIGSTOP cannot be caught(捕捉), blocked(阻塞), or ignored(忽略).
进程在收到一个信号时,有3种处理方式:
1)捕捉信号
把一个信号 与 用户自定义的处理函数相关联
那么当收到这个信号时,就会自动调用该处理函数
2)默认行为
收到一个信号,采用系统默认的行为 (5个)
大部分的信号的默认行为都是终止
3)忽略该信号

2、信号处理的过程

  1. 信号机制实现原理: 是软中断实现的,打断用户程序的执行 <br /> 信号处理函数 ---> 软中断函数 <br /> “进程上下文” :进程环境下 “时间片轮转”<br /> 用户态:<br /> 执行用户自己的代码<br /> 内核态:<br /> 进入到操作系统内核执行的

3、Linux下与信号相关的API函数

1)发送信号
kill
NAME
kill - send signal to a process
SYNOPSIS
#include
#include
int kill(pid_t pid, int sig);
功能:原理发送指定的信号sig给指定的进程
参数:
pid:指定信号的接收者
pid > 0 pid就是表示接收者进程的进程ID
pid == 0 发送信号给与调用进程同组的所有进程
pid == -1 发送信号给所有进程 (有权发送的所有进程)
pid < -1 发送信号给组ID为pid的绝对值的所有进程
例如: pid = -123
发送信号给组ID为123的那个组里面的所有进程
sig:指定要发送的信号值
返回值:
成功,返回0 (至少有一个进程成功收到信号)
失败,返回-1,同时errno被设置
例子:
int main()
{
sleep(3);
kill(getpid(), 9);
}
========================
raise
NAME
raise - send a signal to the caller
SYNOPSIS
#include
int raise(int sig);
功能:发送一个信号给自己本身
参数:
sig:指定要发送的信号值
返回值:
raise() returns 0 on success, and nonzero for failure.
============================
alarm : 定时发送一个闹钟信号给本进程
“闹钟”:每一个进程都有属于自己的闹钟
闹钟时间一到,进程就会收到一个闹钟信号 SIGALRM 同一时刻一个进程只有一个闹钟生效
设置一个闹钟的时间,会自动把是一个闹钟取消
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include
unsigned int alarm(unsigned int seconds);
功能:发送一个闹钟信号SIGALRM给自己
参数:
seconds:设置闹钟的时间,单位:秒
返回值:
返回上一个闹钟剩余的秒数
2)捕捉信号
改变信号处理方式
signal
NAME
signal - ANSI C signal handling
SYNOPSIS
#include
typedef void (*sighandler_t)(int);
sighandler_t是一个新类型
是一个函数指针类型,指向了带有一个int类型的参数,无返回值的函数
void xxx(int sig) //信号处理函数
{
}
sig:信号值
sighandler_t signal(int signum, sighandler_t handler);
功能:把 信号 和 用户自定义的信号处理函数 相关联
参数:
signum:指定要捕捉的信号值
handler:新的信号处理方式
1)自己写一个信号处理函数
2)SIG_DEF 采取系统默认的行为
3)SIG_IGN 忽略该信号
返回值:
成功,返回上一次的处理方式(第一次返回NULL)
失败,返回SIG_ERR,同时errno被设置
注意:
1)在进程的运行期间都可以去捕捉信号
2)可以让不同的信号去共享同一个信号处理函数
练习:
写一个程序,设置一个5s闹钟,捕捉闹钟信号,打印 “起床上课啦”
在用户按下Ctrl C ,进程不死亡,而是打印一句话 “收到了xxx信号”
SIGINT
void my_signal_handler(int sig)
{
}
int main()
{
//设置闹钟
//捕捉信号,关联信号处理函数
while(1);
}

3、消息队列

1、操作 System V IPC的一下相关命令

  1. 查看: <br /> ipcs -q 查看消息队列<br /> ipcs -m 查看共享内存<br /> ipcs -s 查看信号量 <br /> ipcs -a 查看所有ipc<br /> 删除: <br /> ipcrm -q 消息队列id / ipcrm -Q msg_key 删除消息队列<br /> ipcrm -m 共享内存id / ipcrm -Q shm_key 删除共享内存 <br /> ipcrm -s 信号量id / ipcrm -Q sem_key 删除信号量
  2. System V 消息队列,实现为一个带头节点的链表 <br /> 对于系统中的每一个消息队列,内核维护在一个消息结构体中 struct msqid_ds <br /> man msgctl<br /> ---> <br /> struct msqid_ds<br /> {<br /> struct ipc_perm msg_perm; /* 该结构体的读写权限 */<br /> time_t msg_stime; /* 最后发送消息的时间 */<br /> time_t msg_rtime; /* 最后接收消息的时间 */<br /> time_t msg_ctime; /* 最后修改消息结构体的时间 */<br /> unsigned long __msg_cbytes; /* 当前消息队列中消息的总字节数*/<br /> msgqnum_t msg_qnum; /* 当前消息队列中消息(消息结构体)的总数 */<br /> msglen_t msg_qbytes; /* 队列中允许的最大的字节数 */<br /> pid_t msg_lspid; /* 最后发送消息的进程id */<br /> pid_t msg_lrpid; /* 最后接收消息的进程id */<br /> };

2、消息队列的操作

1)申请 System V IPC 的键值 (Key)
键值 的作用就是唯一标识一个System V IPC对象

  1. ftok
  2. NAME<br /> ftok - convert a pathname and a project identifier to a System V IPC key<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> key_t ftok(const char *pathname, int proj_id);<br /> 功能:用一个路径名和一个整数,生成一个key <br /> 参数: <br /> pathname:要一个存在的路径名, 例如 "/home/china"<br /> proj_id:一个整数 <br /> 返回值: <br /> 成功,返回IPC key键值<br /> 失败,返回-1,同时errno被设置 <br /> 注意:<br /> 如果ftok的两个参数相同,那么key键值也相同<br />** 2)创建或打开消息队列 **<br /> msgget
  3. NAME<br /> msgget - get a System V message queue identifier<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/msg.h><br /> int msgget(key_t key, int msgflg);<br /> 功能:用来创建或打开一个System V的消息队列 <br /> 参数:<br /> keySystem V IPC 的键值 <br /> msgflg:标志 <br /> 1)创建 <br /> IPC_CREAT | 权限位 <br /> 例如: <br /> IPC_CREAT | 0666 <br /> 注意: <br /> 如果创建失败的原因 是因为已经存在<br /> 且创建时 IPC_CREAT IPC_EXCL 一起使用 <br /> errno == EEXIST <br /> 2)打开 <br /> 0<br /> 返回值: <br /> 成功,返回一个已经打开的消息队列的id<br /> 失败,返回-1,同时errno被设置
  4. 练习: <br /> 创建一个System V 消息队列
  5. #define pathname "/home/china"
  6. int main()<br /> {<br /> //1.获取key键值<br /> key_t key = ftok( pathname , 1 );<br /> if(key == -1)<br /> {<br /> perror("ftok error");<br /> return -1;<br /> }
  7. printf("key = %x\n", key);
  8. //2.创建一个消息队列<br /> int mid = msgget(key, IPC_CREAT | IPC_EXCL | 0666 );<br /> if(mid == -1)<br /> {<br /> if(errno == EEXIST) //如果已经存在,就直接打开<br /> {<br /> mid = msgget(key, 0);<br /> }<br /> else<br /> {<br /> perror("msgget error");<br /> return -1;<br /> }<br /> }<br /> printf("mid = %d\n", mid);<br /> }<br />** 3)发送 / 接收消息 **<br /> msgsnd / msgrcv
  9. NAME<br /> msgrcv, msgsnd - System V message queue operations<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/msg.h><br /> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);<br /> 功能:用来发送一个消息到指定的消息队列 <br /> 参数: <br /> msqid:消息队列的id,表示要往哪个消息队列上发送消息 <br /> msgp:指向要发送的消息结构体的指针<br /> struct msgbuf <br /> {<br /> long mtype; /* 消息的类型, must be > 0 ,其具体含义也是由程序员来解释*/<br /> char mtext[1]; /* 消息的内容, 可长可短 */<br /> };<br /> msgsz:消息内容的实际长度 (上述结构体中 mtext数组的实际大小)<br /> msgflg:发送标志<br /> 0 阻塞模式 (默认)<br /> IPC_NOWAIT 非阻塞 ,立即返回,errno == ENOMSG <br /> 返回值: <br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  10. 例子: <br /> struct msgbuf buf; //保存要发送的消息 <br /> ...<br /> msgsnd( mid , &buf, sizeof(buf.mtext), 0);
  11. ================================
  12. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);<br /> 功能:用来从消息队列上接收一个消息<br /> 参数:<br /> msqid:消息队列的id<br /> msgp:指向的空间用来保存接收到的消息<br /> msgszmsgp指向的消息结构体中,mtext数组的大小,最多能够保存多少个字节的数据<br /> msgtyp:想要获取的消息的类型 <br /> 0 表示任意是类型<br /> msgflg:接收标志 <br /> 0 阻塞模式 (默认)<br /> IPC_NOWAIT 非阻塞 ,立即返回,errno == ENOMSG<br /> 返回值: <br /> 成功,返回实际接收到的字节数<br /> 失败,返回-1,同时errno被设置
  13. 例子:<br /> struct msgbuf buf; //保存接收到的消息 <br /> msgrcv(mid, &buf, sizeof(buf.mtext), 1003, 0);
  14. 练习: <br /> msg_send.c
  15. struct msgbuf <br /> {<br /> long mtype; /* 消息的类型, must be > 0 ,其具体含义也是由程序员来解释*/<br /> char mtext[128]; /* 消息的内容, 可长可短 */<br /> };<br /> int main()<br /> {<br /> //1.获取key键值
  16. //2.创建或打开一个消息队列
  17. //3.发送消息<br /> for(i=0; i<3; i++)<br /> {<br /> // 1001 1002 1003
  18. }<br /> }<br /> msg_recv.c
  19. struct msgbuf <br /> {<br /> long mtype; /* 消息的类型, must be > 0 ,其具体含义也是由程序员来解释*/<br /> char mtext[128]; /* 消息的内容, 可长可短 */<br /> };
  20. int main()<br /> {<br /> //1.获取key键值
  21. //2.创建或打开一个消息队列
  22. //3.接收消息,并输出<br /> scanf() //获取指定的消息类型<br /> }

4)消息队列的控制操作

  1. msgctl
  2. NAME<br /> msgctl - System V message control operations<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/msg.h><br /> int msgctl(int msqid, int cmd, struct msqid_ds *buf);<br /> 功能:用来在消息队列上提供除了发送和接收以外的其他控制操作 <br /> 参数:<br /> msqid:指定要操作的消息队列的id<br /> cmd:命令号 <br /> IPC_RMID :删除指定的消息队列 <br /> IPC_SET :设置<br /> IPC_STAT :获取属性<br /> ...<br /> buf:根据cmd的不同,而有不同的情况 <br /> if cmd == IPC_RMID, NULL <br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置

4、共享内存

1、共享内存

  1. 进程间通信的一种方式,多个进程共享一块内存, ”共享内存“<br /> 由于多个进程共享一块内存,你往这块内存中写数据,实际就相当于往我的内存中写数据<br /> 少拷贝了一次内存,共享内存的效率更高
  2. 随内核的持续性
  3. 实现方式:<br /> 在内核中开辟一块内存空间,其他的进程通过 ”内存映射“的方式<br /> 获取这个共享内存的首地址
  4. 进程p1可以映射这段内存,同时其他进程p2也可以映射这段内存<br /> p1往这段内存中写数据,实际上就是往p2中写数据

2、System V 共享内存的API函数

  1. System V IPC (msg , shm, sem) 操作流程 <br /> 1)获取IPC的键值 <br /> ftok <br /> 2)创建或打开一个IPC对象 ,获取IPC对象的id
  2. 3)操作 <br /> 发送、接收消息 <br /> 4)控制操作<br /> 删除<br /> =====================================
  3. 共享内存:
  4. ** 1)获取键值 **<br /> ftok
  5. ** 2)创建或打开一个System V 的共享内存 **<br /> shmget
  6. NAME<br /> shmget - allocates a System V shared memory segment<br /> SYNOPSIS<br /> #include <sys/ipc.h><br /> #include <sys/shm.h><br /> int shmget(key_t key, size_t size, int shmflg); <br /> 功能:获取一块共享内存 <br /> 参数:<br /> keySystem V IPC 对象的键值<br /> size:以字节为单位 指定共享内存的大小<br /> 当实际操作是创建一个新的共享内存时,必须指定一个不为0size值<br /> 当实际操作是访问一个已经存在的共享内存时,那么size==0<br /> shmflg:标志位<br /> 1)创建<br /> IPC_CREAT | 权限位 <br /> 例如: <br /> IPC_CREAT | 0666 <br /> 注意: <br /> 如果创建失败的原因 是因为已经存在<br /> 且创建时 IPC_CREAT IPC_EXCL 一起使用 <br /> errno == EEXIST <br /> 2)打开<br /> 0<br /> 返回值:<br /> 成功,返回共享内存的id<br /> 失败,返回-1,同时errno被设置

3)映射共享内存区、解映射
shmat / shmdt

  1. NAME<br /> shmat, shmdt - System V shared memory operations<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/shm.h><br /> void *shmat(int shmid, const void *shmaddr, int shmflg);<br /> 功能:用来映射一个System V共享内存到进程地址空间 <br /> 参数: <br /> shmid:共享内存的id<br /> shmaddr:映射到进程地址空间的哪个地址上<br /> NULL,让它自行分配<br /> shmflg:映射方式<br /> SHM_RDONLY 只读<br /> 0 可读可写 <br /> 返回值:<br /> 成功,返回映射后的地址,这个地址就执行内核中的共享内存的地址<br /> 失败,返回NULL,同时errno被设置
  2. int shmdt(const void *shmaddr);<br /> 功能:解映射共享内存区<br /> 参数:<br /> shmaddr:用来解除映射的地址 shmat的返回值)<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> 练习:<br /> 创建一个共享内存,实现父进程和子进程 利用共享内存来进行通信
  3. 子进程 写入数据 <br /> 父进程 读取数据 打印
  4. #define pathname "/home/china"<br /> int main()<br /> {<br /> //1.获取key键值<br /> key_t key = ftok(pathname, 1);<br /> if(key == -1)<br /> {<br /> perror("ftok error ");<br /> return -1;<br /> }
  5. //2.创建或打开一个共享内存<br /> int shm_id = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666); <br /> if(shm_id == -1)<br /> {<br /> if(errno == EEXIST)<br /> {<br /> shm_id = shmget(key, 0, 0); //已经存在,就直接打开<br /> }<br /> else <br /> {<br /> perror("shmget error ");<br /> return -1;<br /> }<br /> }
  6. //3.映射 <br /> char *p = shmat(shm_id, NULL, 0);<br /> if(p == NULL)<br /> {<br /> perror("shmat error ");<br /> return -1;<br /> }
  7. //4.创建一个子进程<br /> pid_t pid = fork();
  8. //5.通信<br /> if(pid > 0) //父进程<br /> {<br /> int a;<br /> wait(&a); //等待子进程退出
  9. printf("father : ");<br /> printf("%s\n", p);<br /> }<br /> else if(pid == 0) //子进程<br /> {<br /> printf("son : ");<br /> char buf[32] = {0};<br /> fgets(buf, 32, stdin);<br /> strcpy(p, buf);<br /> }<br /> else<br /> {<br /> perror("fork error ");<br /> shmdt(p);<br /> return -1;<br /> }
  10. //6.解映射<br /> shmdt(p);<br /> }
  11. ** 4)共享内存的控制操作 **
  12. shmctl
  13. NAME<br /> shmctl - System V shared memory control<br /> SYNOPSIS<br /> #include <sys/ipc.h><br /> #include <sys/shm.h><br /> int shmctl(int shmid, int cmd, struct shmid_ds *buf);<br /> 功能:共享内存的控制操作<br /> 参数:<br /> shmid:共享内存的id<br /> cmd:命令号<br /> IPC_RMID :删除<br /> IPC_SET :设置<br /> IPC_STAT :获取属性<br /> ...<br /> buf:根据cmd的不同,而有不同的情况 <br /> if cmd == IPC_RMID, NULL
  14. struct shmid_ds <br /> {<br /> struct ipc_perm shm_perm; /* Ownership and permissions */<br /> size_t shm_segsz; /* Size of segment (bytes) */<br /> time_t shm_atime; /* Last attach time */<br /> time_t shm_dtime; /* Last detach time */<br /> time_t shm_ctime; /* Last change time */<br /> pid_t shm_cpid; /* PID of creator */<br /> pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */<br /> shmatt_t shm_nattch; /* No. of current attaches */<br /> ...<br /> };
  15. 作业题:(完成后发到QQ邮箱)
  16. 1,两个进程之间利用共享内存来进行通信<br /> 数据: "123456789"
  17. shm1.c p1 循环的写字符串的一个字符 ,每一次写一个字节
  18. shm2.c p2 不断的接收一个字节<br /> shm1.c <br /> #define pathname "/home/china"<br /> int main()<br /> {<br /> //1.获取key键值<br /> key_t key = ftok(pathname, 1);<br /> if(key == -1)<br /> {<br /> perror("ftok error ");<br /> return -1;<br /> }
  19. //2.创建或打开一个共享内存,获取id<br /> int shm_id = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);<br /> if(shm_id == -1)<br /> {<br /> if(errno == EEXIST)<br /> {<br /> shm_id = shmget(key, 0, 0);//已经存在,就直接打开<br /> }<br /> else<br /> {<br /> perror("shmget error ");<br /> return -1;<br /> }<br /> }
  20. //3.映射<br /> char *p = shmat(shm_id, NULL, 0);<br /> if(p == NULL)<br /> {<br /> perror("shmat error ");<br /> return -1;<br /> }
  21. //4.操作 数据通信<br /> char *s = "123456789";<br /> int i=0;<br /> while(1)<br /> {<br /> *p = *(s+i);<br /> i = (i+1)%9; // i++; if(i==9) i=0;<br /> }
  22. //5.解映射<br /> shmdt(p);<br /> }<br /> shm2.c <br /> ` //4.操作 数据通信<br /> while(1)<br /> {<br /> //printf("%c\n",*p);<br /> fprintf(stderr, "%c", *p); //stderr 标准出错流,无缓冲<br /> }

5、信号量

  1. 如果有两个或两个以上的任务(进程、线程),去访问同一个共享资源(硬件上的、软件上的)<br /> 那么必须保证这个资源的有序访问
  2. 例子: <br /> int i = 0;
  3. func()<br /> {<br /> i++;<br /> }
  4. 当有两个实例任务,调用这个func函数<br /> 那么请问 最后 i的值为多少?<br /> 不确定的 1 2

1、信号量

  1. 信号量 Semaphore <br /> 是用于不同进程间 或者 同一个进程不同线程间 的同步机制 <br /> 同步 ---》 有序、有条件的访问
  2. 信号量的出现 就是为了保护共享资源,让共享资源被有序的访问
  3. 信号量是用来表示一种资源的数量
  4. 什么时候使用信号量?<br /> 要保护对象的时候,需要信号量
  5. ”保护“:让被保护的对象(共享资源)被有序的访问

2、如何来保护?

  1. 信号量机制的其实的程序设计者的约束,用来保护共享资源的
  2. 例子:<br /> 进程A 进程B 都要访问同一个互斥设备
  3. 那么就用一个信号量来表示能不能访问该设备,然后每一个进程在访问这个设备之前,<br /> 先去访问信号量,<br /> 如果能够访问该设备, 则先将信号量调成 NO“,然后再去访问该互斥设备<br /> 完成访问之后,最后再把该信号量调成”YES

3、信号量如何实现?

  1. **一个进程(线程)可以在某一个信号量上执行3个操作 **<br /> 1)创建一个信号量,还必须要求 调用者指定信号量的初值 <br /> 初始值就是表示该信号量保护的共享资源,可以被多少个任务访问
  2. sem >= 5 表示 可以同时被5个进程或线程同时去访问<br /> sem >= 1 表示 可以同时被1个进程或线程同时去访问<br /> 2)等待一个信号量 ,该操作会测试这个信号量的值 <br /> 如果其值是小于或等于0,那么就会阻塞(等待)<br /> 一旦它的值变成大于0,就先将他-1,再继续执行后面访问它的代码<br /> while( sem_value <= 0)<br /> {<br /> wait <br /> }<br /> sem_value --;<br /> p操作 proberen (尝试) 荷兰语 <br /> lock 上锁
  3. 3)释放一个信号量,将信号量的值+1
  4. sem_value ++;
  5. v操作: verhogen (增加) 荷兰语 <br /> unlock 解锁
  6. 信号量保护:<br /> 在临界区的前面加上一个p操作,如何在临界区的后面加上一个v操作
  7. “临界区”:把操作“共享资源”代码区域,称为临界区
  8. p<br /> xxxxxx;<br /> v

4、信号量的实现

  1. System V semaphore <br /> POSIX semaphore <br /> POSIX 有名 semaphore<br /> POSXI 无名 semaphore

4.1)System V semaphore

  1. System V 信号量 ---》 计数信号量集(信号量数组)<br /> 存在于 内核
  2. 对于系统中每个信号量集,内核都会维护在如下的结构体中 <br /> struct semid_ds <br /> {<br /> struct ipc_perm sem_perm; /* 权限 */<br /> time_t sem_otime; /* 最后调用semop() 时间 */<br /> time_t sem_ctime; /* 最后一个改变的时间 */<br /> unsigned long sem_nsems; /* 信号量数组有多少个信号量 */<br /> };<br /> **API函数:**
  3. ** 1)获取key键值 **<br /> ftok
  4. **2)创建或打开一个System V 信号量集 **<br /> semget<br /> NAME<br /> semget - get a System V semaphore set identifier<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/sem.h><br /> int semget(key_t key, int nsems, int semflg);<br /> 功能:创建或打开一个System V 信号量集,获取信号量集的id<br /> 参数:<br /> keySystem V IPC的键值 <br /> nsems:指定要创建的信号量集中信号量的个数<br /> semflg:标志位<br /> 1)创建<br /> IPC_CREAT | 权限位 <br /> 例如: <br /> IPC_CREAT | 0666 <br /> 注意: <br /> 如果创建失败的原因 是因为已经存在<br /> 且创建时 IPC_CREAT IPC_EXCL 一起使用 <br /> errno == EEXIST <br /> 2)打开<br /> 0 <br /> 返回值:<br /> 成功,返回信号量集的id<br /> 失败,返回-1,同时errno被设置
  5. 注意: <br /> 在一个新创建的信号量集中 信号量的值是不确定的<br /> 所以,我们在创建完一个信号量集之后,马上指定它的初始值
  6. ** 3)控制操作 **<br /> semctl <br /> NAME<br /> semctl - System V semaphore control operations<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/sem.h><br /> int semctl(int semid, int semnum, int cmd, ...);<br /> 功能:System V信号量集的控制操作 <br /> 参数:<br /> semid:指定要操作的信号量集id<br /> semnum:指定要操作的哪一个信号量,就是信号量数组的下标 0,1,2,3,....<br /> cmd:命令号 <br /> IPC_STAT 获取属性<br /> IPC_SET 设置属性<br /> IPC_RMID 删除<br /> GETVAL 获取某个信号量的值<br /> SETVAL 设置某个信号量的值<br /> GETALL 获取整个信号量集中所有的信号量的值<br /> SETALL 设置整个信号量集中所有的信号量的值
  7. ...:关于第四个参数<br /> 根据cmd的不同,第四个参数也就不同
  8. 第四个参数的类型 ,根据第三个参数来确定的<br /> union semun <br /> {<br /> int val; /* Value for SETVAL */<br /> struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */<br /> unsigned short *array; /* Array for GETALL, SETALL */<br /> struct seminfo *__buf; /* Buffer for IPC_INFO<br /> (Linux-specific) */<br /> };<br /> 例子: <br /> SETVAL <br /> union semun un;<br /> un.val = 1;<br /> semctl(semid, 0, SETVAL, un.val);<br /> ==================================================================<br /> if cmd == IPC_STAT , 获取信号量集的那个结构体的信息 <br /> 那么第四个参数就应该为 struct semid_ds *buf<br /> buf:执行的空间就是用来保存所获取到的信息 <br /> 例子:<br /> struct semid_ds buf;<br /> semctl(semid, 0, IPC_STAT, &buf);
  9. ================================================================== <br /> if cmd == IPC_SET , 设置信号量集的那个结构体的信息<br /> 那么第四个参数就应该为 struct semid_ds *buf<br /> buf:执行的空间就是用来保存要设置的结构体信息<br /> 例子: <br /> struct semid_ds buf; <br /> buf. = // 给buf结构体的成员进行赋值<br /> ...<br /> semctl(semid, 0, IPC_SET, &buf);
  10. ==================================================================<br /> if cmd == IPC_RMID , 删除信号量集 <br /> 那么第四个参数就不需要
  11. ==================================================================<br /> if cmd == GETVAL , 获取某个信号量的值 那么第四个参数就不要<br /> 函数的返回值 就是该信号量的值<br /> 例子: <br /> int val = semctl(semid, 0, GETVAL);
  12. ==================================================================<br /> if cmd == SETVAL ,设置某个信号量的值, 那么第四个参数为int类型<br /> 例子:<br /> 设置信号量集中第4个信号量的值为1 <br /> int a = 1;<br /> semctl(semid, 3, SETVAL, a);
  13. ==================================================================<br /> if cmd == GETALL , 获取整个信号量集中所有的信号量的值<br /> 那么第四个参数就为 unsigned short [] <br /> 用来存储所获取的所有信号量的值<br /> 例子:<br /> unsigned short val[5];<br /> semctl(semid, 0, GETALL, val);
  14. ==================================================================<br /> if cmd == SETALL ,设置整个信号量集中所有的信号量的值<br /> 那么第四个参数就为 unsigned short [] <br /> 用来存储要设置的所有信号量的值<br /> 例子:<br /> unsigned short val[5] = {1,2,3,4,5};<br /> semctl(semid, 0, SETALL, val);
  15. 练习:<br /> 创建一个信号量集(5个),<br /> 去设置所有的信号量的值 <br /> 获取第三个信号量的值
  16. #define pathname "/home/china"<br /> int main()<br /> {<br /> //1.获取key键值<br /> key_t key = ftok(pathname, 1);<br /> if(key == -1)<br /> {<br /> perror("ftok error ");<br /> return -1;<br /> }<br /> //2.创建或打开一个信号量集<br /> int sem_id = semget(key, 5, IPC_CREAT | IPC_EXCL | 0666);<br /> if(sem_id == -1)<br /> {<br /> if(errno == EEXIST)<br /> {<br /> sem_id = semget(key, 5, 0);<br /> }<br /> else <br /> {<br /> perror("semget error ");<br /> return -1;<br /> }<br /> }
  17. //3.操作<br /> //去设置所有的信号量的值 SETALL<br /> unsigned short val[5] = {1,3,5,7,9};<br /> semctl(sem_id, 0, SETALL, val);
  18. //获取第三个信号量的值 GETVAL<br /> int r = semctl(sem_id, 2, GETVAL);<br /> printf("%d\n",r);<br /> r = semctl(sem_id, 4, GETVAL);<br /> printf("%d\n",r);
  19. }
  20. ** 4)信号量的p/v操作 **<br /> semop
  21. NAME<br /> semop - System V semaphore operations<br /> SYNOPSIS<br /> #include <sys/types.h><br /> #include <sys/ipc.h><br /> #include <sys/sem.h><br /> int semop(int semid, struct sembuf *sops, size_t nsops);<br /> 功能:对 System V 信号量的p/v操作 <br /> 参数:<br /> semid:指定要操作的信号量集id<br /> sops:结构体指针,信号量的p/v操作的结构体数组<br /> 可能同时对多个信号量进行操作 <br /> System V信号量集中,对于某个信号量的p/v操作<br /> 用结构体来描述 <br /> struct sembuf<br /> {<br /> unsigned short sem_num; /* 指定要操作的哪一个信号量,就是信号量数组的下标 */<br /> short sem_op; /* 操作 */<br /> sem_value = 原来的sem_value + sem_op
  22. sem_op < 0 ===> - p操作<br /> sem_op > 0 ===> + v操作 <br /> sem_op == 0 ===> 如果原来sem_value == 0, 立即返回<br /> 如果原来sem_value != 0,看阻塞还是非阻塞
  23. short sem_flg; /* 标志 */<br /> 1 0 阻塞 (默认)<br /> 2 IPC_NOWAIT 非阻塞<br /> 如果是p操作,能获取则获取,不能获取返回-1 <br /> 3SEM_UNDO 撤销 <br /> 为了防止进程带锁退出<br /> 如果设置SEM_UNDO,内核会额外记录<br /> 该进程对该信号量的p/v操作<br /> 在进程退出的时候,会自动还原 <br /> };
  24. nsops:第二个参数需要操作的信号量的个数<br /> 表示你需要对多少个信号量进行p/v操作 <br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> 例子:<br /> p操作 上锁
  25. struct sembuf buf;<br /> buf.sem_num = 0; //表示要对哪个信号量进行操作,下标 第一个<br /> buf.sem_op = -1; //p操作<br /> buf.sem_flg = 0; //阻塞
  26. semop(semid, &buf, 1);
  27. // 临界区 <br /> //对共享资源的操作 ....
  28. v操作 解锁 <br /> struct sembuf buf;<br /> buf.sem_num = 0; //表示要对哪个信号量进行操作,下标 第一个<br /> buf.sem_op = +1; //v操作<br /> buf.sem_flg = 0; //阻塞
  29. semop(semid, &buf, 1);
  30. 作业题:(完成后发到QQ邮箱)
  31. 2,两个进程之间利用共享内存来进行通信<br /> 数据: "123456789"
  32. shm1.c p1 循环的写字符串的一个字符 ,每一次写一个字节
  33. shm2.c p2 不断的接收一个字节 <br /> 实现有序的输出
  34. sem_shm1.c <br /> //p操作 写<br /> struct sembuf buf; // buf[0]读信号量,buf[1]写的信号量<br /> buf.sem_num = 1; //对哪个信号量进行操作<br /> buf.sem_op = -1; //p操作 <br /> buf.sem_flg = 0;<br /> semop(sem_id, buf, 1);<br /> //临界区<br /> *p = *(s+i);<br /> i = (i+1)%9; // i++; if(i==9) i=0;<br /> //v操作 读<br /> buf.sem_num = 0; //对哪个信号量进行操作<br /> buf.sem_op = +1; //v操作 <br /> buf.sem_flg = 0;<br /> semop(sem_id, &buf, 1);
  35. sem_shm2.c <br /> //p操作 读<br /> struct sembuf buf; // buf[0]读信号量,buf[1]写的信号量<br /> buf.sem_num = 0; //对哪个信号量进行操作<br /> buf.sem_op = -1; //p操作 <br /> buf.sem_flg = 0;<br /> semop(sem_id, &buf, 1);<br /> //临界区<br /> //printf("%c\n",*p);<br /> fprintf(stderr, "%c", *p); //stderr 标准出错流,无缓冲<br /> //v操作 写<br /> buf.sem_num = 1; //对哪个信号量进行操作<br /> buf.sem_op = +1; //v操作 <br /> buf.sem_flg = 0;<br /> semop(sem_id, &buf, 1);

4.2)POSIX semaphore ——> 单个信号量

  1. POSIX 有名 semaphore<br /> 可以用于任意的进程之间 以及 任意的线程间 <br /> 在文件系统中有一个入口(有一个文件名),但是 信号量的值存在于内核
  2. POSXI 无名 semaphore <br /> 线程、子进程间<br /> 没有名字,基于内存的信号量
  3. 如果这段空间在一个“内核的共享内存中”,进程可以访问,线程也可以访问 <br /> 如果这段空间在一个“进程的地址空间中”,只能用于该进程内部的所有线程的同步

1)创建或打开一个POSIX信号量

  1. POSIX信号量用类型 sem_t来表示,不管是有名还是无名 <br /> **1.1)创建并初始化POSIX有名信号量 **<br /> sem_open
  2. NAME<br /> sem_open - initialize and open a named semaphore++<br /> SYNOPSIS<br /> #include <fcntl.h> /* For O_* constants */<br /> #include <sys/stat.h> /* For mode constants */<br /> #include <semaphore.h><br /> sem_t *sem_open(const char *name, int oflag);<br /> sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);<br /> 功能:创建并初始化一个POSIX有名信号量<br /> 参数:<br /> name:要创建或打开的POSIX有名信号量在文件系统中的路径名 <br /> "/" 开头的路径名 ,(实际生成在 /dev/shm/ 目录下)<br /> 例如:<br /> "/xxx"
  3. oflag:标志位 <br /> 1)打开 <br /> 0 <br /> 2)创建 <br /> O_CREAT <br /> 要判断是否已经存在 <br /> O_CREAT | O_EXCL <br /> mode:权限 ,有两种方式指定 <br /> 1)<br /> S_IRWXU<br /> S_IRUSR<br /> S_IWUSR <br /> ...<br /> 2)八进制 <br /> 0666 <br /> value:信号量的初始值<br /> 返回值:<br /> 成功,返回一个sem_t的指针,指向一个已经打开的POSIX有名信号量 <br /> 失败,返回SEM_FAILED, 同时errno被设置
  4. Link with -pthread.<br /> 编译时,要链接库 -pthread ====> posix thread
  5. **1.2)初始化POSIX无名信号量 **<br /> sem_init
  6. NAME<br /> sem_init - initialize an unnamed semaphore<br /> SYNOPSIS<br /> #include <semaphore.h>
  7. 初始化一个POSIX无名信号量,需要事先分配一个sem_t 结构体空间 <br /> sem_t st; 或者 sem_t *st = malloc(sizeof(sem_t));
  8. int sem_init(sem_t *sem, int pshared, unsigned int value);<br /> 功能:初始化一个POSIX无名信号量 <br /> 参数:<br /> sem:要初始化的无名信号量的首地址 <br /> pshared:该无名信号量的共享方式<br /> 0 进程内部的线程之间的共享
  9. 1(非0)不同进程间的共享<br /> 注意:如果是这种情况的<br /> 要保证sem指向的空间是不同进程都能够访问的<br /> ===》 sem指向的空间只能是共享内存<br /> value:信号量的初始值<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Link with -pthread.<br /> 编译时,要链接库 -pthread

2)POSIX信号量的 p/v 操作

  1. ** 2.1p操作 **<br /> sem_wait <br /> NAME<br /> sem_wait, sem_timedwait, sem_trywait - lock a semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_wait(sem_t *sem); //一直等待直到申请到资源<br /> 功能:用来获取sem指定的信号量,阻塞直到获取该信号量<br /> 返回值:<br /> 成功,返回0<br /> 失败返回-1,同时errno被设置<br /> int sem_trywait(sem_t *sem);<br /> 功能:用来获取sem指定的信号量,不会阻塞 <br /> 能获取就获取(返回0)<br /> 不能获取则立马返回(返回-1,同时errno被设置)
  2. int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);<br /> 功能:用来获取sem指定的信号量,限时等待<br /> 参数:<br /> sem:指定要操作的信号量的结构体指针<br /> abs_timeout:是一个绝对时间 <br /> 获取当前的时间(距离1970.1.1 经历的秒数) + 愿意等待的时间<br /> struct timespec <br /> {<br /> time_t tv_sec; /* 秒 */<br /> long tv_nsec; /* 纳秒 [0 .. 999999999] */<br /> };<br /> 1s == 1000ms == 1000000us == 1000000000ns <br /> =====================================<br /> NAME<br /> clock_gettime - clock and time functions<br /> SYNOPSIS<br /> #include <time.h><br /> int clock_gettime(clockid_t clk_id, struct timespec *tp);<br /> 功能:获取时间<br /> clk_id:<br /> CLOCK_REALTIME
  3. 例子:获取当前的时间 <br /> struct timespec tm;<br /> clock_gettime(CLOCK_REALTIME, &tm);
  4. 返回值:<br /> 成功,返回0<br /> 失败返回-1,同时errno被设置
  5. Link with -pthread.<br /> 编译时,要链接库 -pthread
  6. 例子:等待 3s 40ms <br /> struct timespec tm;<br /> clock_gettime(CLOCK_REALTIME, &tm);//获取当前的时间 <br /> tm.tv_sec += 3;<br /> tm.tv_nsec += 40000000; //40ms <br /> if(tm.tv_nsec >= 1000000000 ) //考虑是否有进位<br /> {<br /> tm.tv_nsec -= 1000000000;<br /> tm.tv_sec++;<br /> }
  7. sem_timedwait(sem, &tm);<br />** 2.2) v操作 **<br /> sem_post <br /> NAME<br /> sem_post - unlock a semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_post(sem_t *sem);<br /> 功能:用来对sem指定的信号量进行v操作 <br /> 参数:<br /> sem:指定要操作的信号量的结构体指针<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Link with -pthread.
  8. 练习:<br /> 两个进程之间利用共享内存来进行通信<br /> 数据: "123456789"
  9. posix_sem1.c p1 循环的写字符串的一个字符 ,每一次写一个字节
  10. posix_sem2.c p2 不断的接收一个字节 <br /> POSIX有名信号量 实现有序的输出
  11. int main()<br /> {<br /> //...
  12. //创建或打开POSIX有名信号量 <br /> // /data 读<br /> // /space 写
  13. }

3)POSIX信号量的其他操作

  1. ** 3.1)用来获取信号量的值 **<br /> sem_getvalue<br /> NAME<br /> sem_getvalue - get the value of a semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_getvalue(sem_t *sem, int *sval);<br /> 功能:用来获取sem指定的POSIX信号量的值,保存在sval指向的空间中<br /> 参数:<br /> sem:指定要获取值的信号量指针<br /> sval:指向的空间用来保存信号量的值<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  2. Link with -pthread.
  3. **3.2POSIX有名信号量的关闭和删除操作**<br /> 关闭 sem_close
  4. NAME<br /> sem_close - close a named semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_close(sem_t *sem);<br /> 功能:关闭一个POSIX有名信号量<br /> 参数:<br /> sem:指定要关闭的信号量指针<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  5. Link with -pthread.
  6. =========================
  7. 删除 sem_unlink
  8. NAME<br /> sem_unlink - remove a named semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_unlink(const char *name);<br /> 功能:删除一个POSIX有名信号量<br /> 参数:<br /> sem:指定要删除的信号量名字<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  9. Link with -pthread.<br /> ** 3.3POSIX无名信号量的销毁**
  10. sem_destroy
  11. NAME<br /> sem_destroy - destroy an unnamed semaphore<br /> SYNOPSIS<br /> #include <semaphore.h><br /> int sem_destroy(sem_t *sem);<br /> 功能:销毁一个POSIX无名信号量<br /> 参数:<br /> sem:指定要销毁的信号量指针<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  12. Link with -pthread.

6、线程

1、 线程

  1. Linux内核中,所有的调度的实体都称为任务,他们之间的区别:<br /> 有些任务各自拥有一套资源,而有些任务之间共享同一套资源
  2. 为了并发执行任务,现代操作系统引入“进程”的概念
  3. 分析:<br /> 1)进程的地址空间是独立的 <br /> 创建一个进程的系统开销是比较大的,因为要拷贝父进程的地址空间
  4. 2)进程的地址空间是独立的,分开的 <br /> 进程间需要进行数据交换,则需要用到进程间通信(pipe,fifo,...)<br /> 进程间通信的代价比较大
  5. 能不能在同一个地址空间内实现“任务”的并发执行<br /> “线程”/“轻量级进程”

2、线程的概念

  1. 1)线程是比进程更加小的活动单位,它是进程的执行分支<br /> 2)线程同进程内部的其他线程 共享进程的地址空间的
  2. **线程的特点:**<br /> 1)创建一个线程 比创建一个进程的开销要小很多
  3. 2)实现线程的通信十分方便,因为一个进程创建的多个线程直接共享进程的内存区域
  4. 3)线程是一个动态的概念<br /> 进程内部的执行分支,线程是用来在进程内部来并发执行指令<br /> C语言的指令只能在函数内部 ----》 线程 对应了一个函数 (线程函数)<br /> 线程的主要工作就是去执行那个线程函数,如果线程函数执行完了,<br /> 那么线程也就不存在了
  5. 4)在进程内创建多线程,可以提高系统的并行处理能力,加快进程的处理速度<br /> 系统是按线程来进行调度
  6. 5)进程默认有一个主线程(main函数),在进程的运行过程中,可以创建其他的线程
  7. 6)在操作系统的内部,进程是分配系统资源的最小单位,线程是调度的最小单位

3、Linux下线程的接口函数

  1. pthreadPOSIX实现的一套线程机制 POSIX thread ----》 pthread

1)创建一个线程
pthread_create

  1. 创建线程需要指定什么东西:<br /> 线程函数<br /> 线程属性
  2. pthread,用类型 pthread_t 来表示一个线程的id <br /> pthread_attr_t 来表示一个线程的属性 (线程栈空间的大小/分离状态/线程的优先级...)<br /> “默认属性”<br /> NAME<br /> pthread_create - create a new thread<br /> SYNOPSIS<br /> #include <pthread.h>
  3. int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);<br /> 功能:创建一个线程<br /> 参数:<br /> thread:指向的空间是用来保存新创建的线程的id<br /> attr:用来指定新创建的线程的属性 <br /> 一般填NULL,表示默认属性 <br /> start_routine:函数指针<br /> 它指向的函数就是新创建的线程要指向的线程函数
  4. 线程函数类型应该为:带一个参数为void*,返回一个void*的返回值<br /> void* xxx(void *arg)<br /> {
  5. }<br /> arg:指定线程函数的参数<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  6. 注意:一旦线程创建成功,那么线程函数就会立即被执行
  7. Compile and link with -pthread.

2)线程的退出
(1) 线程函数返回
(2) 调用 pthread_exit()
(3) 被别人取消,别的线程调用 pthread_cancel()
2.2) pthread_exit()

  1. NAME<br /> pthread_exit - terminate calling thread<br /> SYNOPSIS<br /> #include <pthread.h><br /> void pthread_exit(void *retval);<br /> 功能:线程退出<br /> 参数:<br /> retval:指向的空间用来存储线程退出的退出值<br /> 注意:存放退出值的空间一定要是随进程的持续性
  2. Compile and link with -pthread.<br /> 2.3)被别人取消,别的线程调用 pthread_cancel() <br /> NAME<br /> pthread_cancel - send a cancellation request to a thread<br /> SYNOPSIS<br /> #include <pthread.h><br /> int pthread_cancel(pthread_t thread);<br /> 功能:发送一个取消请求给thread指定的线程<br /> 参数:<br /> thread:指定要取消的线程的id<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Compile and link with -pthread.
  3. =========================<br /> pthread_cancel()是用来发送一个取消请求给thread指定的线程,<br /> 但是接收到的整个请求的线程是否结束:<br /> 取决于线程的属性 <br /> cancel state (是否可被取消)<br /> NAME<br /> pthread_setcancelstate, pthread_setcanceltype - set cancelability state and type<br /> SYNOPSIS<br /> #include <pthread.h><br /> int pthread_setcancelstate(int state, int *oldstate);<br /> 功能:设置线程的取消属性<br /> 参数:<br /> state:属性 <br /> PTHREAD_CANCEL_ENABLE 可取消 (默认)<br /> PTHREAD_CANCEL_DISABLE 不可取消(忽略这个取消请求)<br /> oldstate:指向的空间是用来保存之前的属性 <br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  4. int pthread_setcanceltype(int type, int *oldtype);<br /> 功能:设置线程取消的类型<br /> 参数:<br /> type:<br /> PTHREAD_CANCEL_DEFERRED 延迟取消<br /> PTHREAD_CANCEL_ASYNCHRONOUS 立即取消(默认)<br /> oldstate:指向的空间是用来保存之前的类型<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Compile and link with -pthread.
  5. ==============================<br /> NAME<br /> pthread_self - obtain ID of the calling thread<br /> SYNOPSIS<br /> #include <pthread.h><br /> pthread_t pthread_self(void);<br /> 功能:获取当前调用线程的id<br /> 参数:无<br /> 返回值:返回当前调用线程的id
  6. Compile and link with -pthread.

3)等待一个线程退出 pthread_join()
指定的线程还在运行,那么就会阻塞

  1. NAME<br /> pthread_join - join with a terminated thread<br /> SYNOPSIS<br /> #include <pthread.h><br /> int pthread_join(pthread_t thread, void **retval);<br /> 功能:用来等待thread指定的那个线程退出<br /> 参数:<br /> thread:指定要等待的线程id<br /> retval:二级指针 <br /> 用来保存一级指针的地址,这一个一级指针用来保存退出值的地址<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Compile and link with -pthread. <br /> ====================<br /> pthread_join 有两个作用<br /> (1)等待线程退出<br /> (2)回收被等待的线程的资源 <br /> 线程退出,不代表线程的资源被释放,这个取决于线程的属性 <br /> detach state (分离属性)
  2. PTHREAD_CREATE_DETACHED : 分离状态<br /> 属于该状态的线程,在其退出后,资源会自动释放<br /> 如果一个线程为分离状态,其他线程调用这个pthread_join()仅仅只是等待线程退出
  3. PTHREAD_CREATE_JOINABLE :非分离状态<br /> 属于该状态的线程,在其退出后,资源不会自动释放<br /> 需要其他线程调用pthread_join() 回收资源 <br /> 注意:默认状态下退出之后,会变成僵尸进程,是非分离状态
  4. =================================<br /> pthread_detach:设置分离状态
  5. NAME<br /> pthread_detach - detach a thread<br /> SYNOPSIS<br /> #include <pthread.h><br /> int pthread_detach(pthread_t thread);<br /> 功能:用来设置分离状态<br /> 参数:<br /> thread:指定要设置的线程id<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> Compile and link with -pthread.
  6. 练习:<br /> 同步打印字符 <br /> 主线程 以%d打印<br /> 子线程 以%c打印
  7. int a = 33; //线程的共享资源 if a>122 结束

4、线程间的同步机制

  1. 1)<br /> 线程之间经常需要去访问一些共享资源,为了实现对共享资源的有序访问<br /> 防止竞争,需要对共享资源的访问进行某种保护
  2. (1)信号量的机制<br /> System V 信号量<br /> POSIX 有名信号量<br /> POSIX 无名信号量
  3. (2)线程互斥锁 <br /> 线程互斥锁存在于进程地址空间的一种保护设施
  4. sudo apt-get install manpages-posix-dev
  5. POSIX thread 用类型 pthread_mutex_t 表示一个线程互斥锁 (存在进程内部)<br /> 只能用于进程内部的不同线程之间的互斥

2)线程互斥锁的相关API函数:

  1. (1)初始化/销毁一个线程互斥锁 <br /> pthread_mutex_init / pthread_mutex_destroy
  2. #include <pthread.h>
  3. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);<br /> 功能:给mutex指定的线程互斥锁进行初始化<br /> 参数:<br /> mutex:指针,指向的对象就是要初始化的线程互斥锁<br /> attr:<br /> 用类型pthread_mutexattr_t来表示一个线程互斥锁的属性<br /> 一般为NULL,表示默认属性<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  4. 例子:<br /> pthread_mutex_t mutex1;<br /> pthread_mutex_init(&mutex1, NULL);
  5. int pthread_mutex_destroy(pthread_mutex_t *mutex);<br /> 功能:销毁mutex指定的线程互斥锁 <br /> 参数:<br /> mutex:指针,指向的对象就是要销毁的线程互斥锁<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置<br /> (2) p/v操作
  6. p操作:<br /> int pthread_mutex_lock(pthread_mutex_t *mutex);<br /> 功能:给互斥锁 上锁 阻塞<br /> 获取锁,获取到锁(返回0)<br /> 否则等待指定获取该互斥锁或者出错(返回-1,同时errno被设置)
  7. int pthread_mutex_trylock(pthread_mutex_t *mutex);<br /> 功能:给互斥锁 上锁 非阻塞 <br /> 获取锁,能获取则获取(返回0)<br /> 不能获取则立即返回(返回-1,同时errno被设置)
  8. v操作:<br /> int pthread_mutex_unlock(pthread_mutex_t *mutex);<br /> 功能:给互斥锁 解锁
  9. 作业:<br /> 利用线程互斥锁,实现同步打印字符 <br /> 主线程 以%d打印<br /> 子线程 以%c打印
  10. int a = 33; //线程的共享资源 if a>122 结束

5、生产者-消费者模型

  1. 生产者负责 产生 数据<br /> 消费者负责 消耗 数据
  2. 1)缓冲区是一个共享资源 “互斥问题”
  3. 信号量 / 线程互斥锁
  4. 2)当缓冲区中没有数据,消费者线程该怎么办?<br /> (1)不停的去测试,看是否有数据来 ====》 “轮询” <br /> 浪费CPU
  5. (2)让出CPU(休眠),当有数据产生时,再唤醒(通知对方)<br /> ===》 需要用到 线程条件变量

6、线程条件变量

  1. 线程条件变量是用来实现多线程之间同步的一种机制<br /> 在多线程出现设计中,用条件变量来表示一种特定的“条件”
  2. pthread 中条件变量的类型为 pthread_cond_t <br /> 在代码中需要声明一个变量来表示一个 “特定的条件”的时候<br /> 可以声明一个 pthread_cond_t 的变量
  3. 条件变量的操作:<br /> (1)初始化/销毁一个条件变量<br /> (2)等待一个条件变量 ===》 等待条件产生 <br /> (3)唤醒一个条件变量 ===》 条件产生,唤醒等待条件变量的线程
  4. 线程1 线程2<br /> if(条件不满足) 产生条件变量<br /> { wake up (条件变量)<br /> wait(条件变量)<br /> }
  5. ========================<br /> 条件变量的相关接口函数:
  6. 1)初始化/销毁一个条件变量<br /> pthread_cond_init / pthread_cond_destroy
  7. #include <pthread.h>
  8. int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);<br /> 功能:用来初始化cond指定的条件变量<br /> 参数:<br /> cond:指针,指向要初始化的条件变量<br /> attr:指针 <br /> pthread_condattr_t*的指针,表示要初始化的条件变量的属性<br /> 一般为NULL,采用默认属性<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  9. int pthread_cond_destroy(pthread_cond_t *cond);<br /> 功能:用来销毁cond指定的条件变量<br /> 参数:<br /> cond:指针,指向要销毁的条件变量<br /> 返回值:<br /> 成功,返回0<br /> 失败,返回-1,同时errno被设置
  10. 2)等待一个条件变量 ===》 等待条件产生 <br /> pthread_cond_wait / pthread_cond_timedwait
  11. pthread_cond_wait 用来等待cond指定的条件<br /> 因为条件变量本身也是一个“共享资源”,为了避免竞争,需要一个线程互斥锁来保护的<br /> 在调用pthread_cond_wait之前,必须先加锁
  12. pthread_cond_wait / pthread_cond_timedwait需要把锁住的锁传入<br /> 在其内部实现的时候,再让线程休眠(让出CPU),会释放该互斥锁,然后休眠<br /> 直到被别人唤醒,被唤醒的时候,再次锁住该互斥锁,并从等待队列返回
  13. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);<br /> 功能:用来等待cond指定的条件 阻塞等待 <br /> 参数:<br /> cond:指针,指向要等待的条件变量<br /> mutex:指针,指向一个的线程互斥锁
  14. int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,<br /> const struct timespec *abstime);<br /> 功能:用来等待cond指定的条件 延时等待 <br /> 参数:<br /> cond:<br /> mutex:<br /> abstime:<br /> 获取当前的时间(距离1970.1.1 经历的秒数) + 愿意等待的时间<br /> struct timespec <br /> {<br /> time_t tv_sec; /* 秒 */<br /> long tv_nsec; /* 纳秒 [0 .. 999999999] */<br /> };<br /> 1s == 1000ms == 1000000us == 1000000000ns <br /> =====================================<br /> NAME<br /> clock_gettime - clock and time functions<br /> SYNOPSIS<br /> #include <time.h><br /> int clock_gettime(clockid_t clk_id, struct timespec *tp);<br /> 功能:获取时间<br /> clk_id:<br /> CLOCK_REALTIME
  15. 例子:获取当前的时间 <br /> struct timespec tm;<br /> clock_gettime(CLOCK_REALTIME, &tm);
  16. 例子:等待 3s 40ms <br /> struct timespec tm;<br /> clock_gettime(CLOCK_REALTIME, &tm);//获取当前的时间 <br /> tm.tv_sec += 3;<br /> tm.tv_nsec += 40000000; //40ms <br /> if(tm.tv_nsec >= 1000000000 ) //考虑是否有进位<br /> {<br /> tm.tv_nsec -= 1000000000;<br /> tm.tv_sec++;<br /> }
  17. pthread_cond_timedwait(&cond, &mutex, &tm);
  18. 3)唤醒一个条件变量 ===》 条件产生,唤醒等待条件变量的线程
  19. int pthread_cond_signal(pthread_cond_t *cond;<br /> 功能:用来唤醒cond指定的条件变量上的一个线程
  20. int pthread_cond_broadcast(pthread_cond_t *cond);<br /> 功能:用来唤醒cond指定的条件变量上的所有线程
  21. 练习题:<br /> int data = 0; //共享资源
  22. main主线程<br /> data
  23. if data >= 100000000<br /> 唤醒线程1
  24. 线程1 <br /> if data < 100000000 <br /> 等待
  25. 当被唤醒后,打印提示<br /> data = 0
  26. int data = 0; //共享资源
  27. void *routine_1(void *arg)<br /> {
  28. while(1)<br /> {
  29. if()
  30. }
  31. }
  32. int main()<br /> {<br /> //初始化互斥锁、条件变量
  33. //创建线程
  34. while(1)<br /> {
  35. data++;
  36. }
  37. }
  38. 练习题:<br /> 利用线程实现两个目录的拷贝(只考虑目录和普通文件的情况)
  39. cp -rf /src /dest
  40. cp_dir(char *src, char *dest)<br /> {<br /> //创建要拷贝的文件夹
  41. //打开目录src
  42. //读取目录项 <br /> while()<br /> {<br /> //过滤.和..
  43. //普通文件 <br /> //合成各自的文件路径名<br /> //创建线程 拷贝文件 cp_file
  44. //目录<br /> cp_dir(); //调用自己<br /> }
  45. //关闭目录<br /> }

7、线程池 thread pool

  1. 线程池是一种多线程的处理方式<br /> 处理过程中将任务添加到队列上,然后在创建线程后自动去执行这些任务
  2. 线程池函数接口:<br /> (1)初始化线程池 <br /> init_pool()
  3. (2)添加任务<br /> add_task()
  4. (3)销毁线程池<br /> destroy_pool()
  5. 注意:<br /> 1)任务队列中最开始是没有任务的,是一个具有首结点空链表<br /> 2)活动线程执行完任务之后不会退出,继续向生产者索要任务(生产者没有任务就等待)<br /> 3)使用互斥锁来保护队列<br /> 4)使用条件变量来代表任务队列上任务个数的变化<br /> 5)通过一个公共开关 shutdown,来控制线程的退出,进而销毁整个线程池
  6. 代码实现
  7. thread_pool.c <br /> thread_pool.h <br /> main.c
  8. ========================<br /> NAME<br /> pthread_cleanup_push, pthread_cleanup_pop - push and pop thread cancel‐<br /> lation clean-up handlers<br /> SYNOPSIS<br /> #include <pthread.h><br /> void pthread_cleanup_push(void (*routine)(void *), void *arg);<br /> 功能:注册线程清理处理程序 防止带锁退出<br /> 参数:<br /> routine:函数指针 ,指向的函数就是线程清理函数<br /> arg:清理函数的参数
  9. 注意:<br /> 当线程执行以下操作时,会执行清理函数<br /> 1)调用 pthread_exit() <br /> 2) 响应取消时候 (被别人调用 pthread_cancel的时候)<br /> 3)用非零的参数调用 pthread_cleanup_pop()
  10. void pthread_cleanup_pop(int execute);
  11. 注意:<br /> execute为非0时,清理函数会被调用<br /> execute0时,清理函数不会被调用<br /> Compile and link with -pthread.