主要进程间通信的通信机制
Linux操作系统支持的主要进程间通信的通信机制
IPC对象,会在当前系统中可见,只要不删除或者不关闭系统,就会一直存在
查看已经创建的IPC对象
查看已经创建的IPC对象
ipcs 查看当前系统中所有创建的IPC对象
ipcs -q 查看创建的消息队列
ipcs -m 查看创建的共享内存
ipcs -s 查看信号量ipcrm 删除IPC对象
例如:ipcrm -q msqid 删除标号为msqid的消息队列
消息队列
消息队列:是消息的链表,存放在内存中,由内核维护
特点:
- 1、消息队列中的消息是有类型的
- 2、消息队列中的消息是有格式的
- 3、消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取
- 4、消息队列允许一个或多个进程向它写入或者读取消息
- 5、与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除
- 6、每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的
- 7、只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中
- System V提供的IPC通信机制需要一个key值,通过key值就可在系统内获得一个唯一的消息队列标识符。
- key值可以是人为指定的,也可以通过ftok函数获得
ftok函数
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- 功能:获得项目相关的唯一的IPC键值
- 参数:
- pathname:路径名
- proj_id:项目id,非0整数(只有低8位有效)
- 返回值:成功返回 key值,失败返回 -1
如果使用ftok函数获取键值,得到的键值是有ftok的第一个参数对应文件的信息和第二个参数一起决定的
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
printf("key = %#x\n", mykey); // key = 0x64010006
return 0;
}
创建消息队列-msgget
创建消息队列-msgget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
-
功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的key值就能得到同一个消息队列的标识符
-
参数:
- key:IPC键值,唯一的键值确定唯一的消息队列
方法1:任意指定一个数
方法2:使用ftok函数获取键值- msgflg:标识函数的行为及消息队列的权限
msgflg的取值:
IPC_CREAT:创建消息队列
IPC_EXCL:检测消息队列是否存在
位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,格式和open函数的mode_t一样,但可执行权限未使用 -
返回值:
- 成功:消息队列的标识符
- 失败:返回 -1
msgflg:消息队列的访问权限
一般设置为:
IPC_CREAT | IPC_EXCL | 0777 或者 IPC_CREAT | 0777
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
printf("mykey = %#x\n", mykey);
// 通过msgget函数创建一个消息队列
int msqid1;
if((msqid1 = msgget(mykey, IPC_CREAT | 0666)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid1 = %d\n", msqid1);
system("ipcs -q");
return 0;
}
ipcs -q 查看创建的消息队列
ipcrm -q msqid 删除标号为msqid的消息队列
消息队列-发送消息msgsnd
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
-
功能:将新消息添加到消息队列
-
参数:
- msqid:消息队列的标识符
- msgp:待发送消息结构体的地址
- msgsz:消息正文的字节数,不包括消息的编号长度
- msgflg:函数的控制属性
0:msgsnd调用阻塞直到条件满足为止
IPC_NOWAIT:若消息没有立即发送则调用该函数的进程会立即返回 -
返回值:
- 成功:0
- 失败:返回 -1
消息队列的消息格式
typedef struct_msg
{
long mtype; /* 消息类型 /
char mtext[100]; / 消息正文 /
… / 消息的正文可以有多个成员 */
}MSG;
消息类型必须是长整型的,而且必须是结构体类型的第一个成员,类型下面是消息正文,正文可以有多个成员(正文成员可以是任意数据类型的)
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
long msg_type; // 消息类型,必须在结构体的第一个位置并且类型必须是long
char msg_text[N]; // 消息正文
}MSG;
#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
printf("mykey = %#x\n", mykey);
// 通过msgget函数创建一个消息队列
int msqid1;
if((msqid1 = msgget(mykey, IPC_CREAT | 0666)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid1 = %d\n", msqid1);
system("ipcs -q");
// 使用msgsnd函数向消息队列中发送数据(写操作)
MSG msg1={1,"hello world"};
MSG msg2={4,"beijing"};
MSG msg3={2,"hello kitty"};
MSG msg4={3,"welcome to xxxx"};
if(msgsnd(msqid1, &msg1, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if(msgsnd(msqid1, &msg2, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if(msgsnd(msqid1, &msg3, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
if(msgsnd(msqid1, &msg3, MSGTEXT_SIZE, 0) == -1)
{
perror("fail to msgsnd");
exit(1);
}
system("ipcs -q");
return 0;
}
消息队列-接收消息msgrcv
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
-
功能:
- 从标识符为msqid的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中被删除
-
参数:
- msqid:消息队列的标识符,代表要从哪个消息队列中获取消息
- msgp:存放消息结构体的地址
- msgsz:消息正文的字节数
- msgtyp:消息的类型、可以有以下几种类型
msgtyp = 0:返回队列中第一个消息
msgtyp > 0:返回队列中消息类型为msgtyp的消息
msgtyp < 0:返回队列中消息类型值小于或等于msgtyp绝对值的消息,如果这种消息有若干个,则取类型值最小的消息 -
msgflg:函数的控制属性
0:msgrcv调用阻塞直到接收消息成功为止
MSG_NOERROR:若返回的消息字节数比nbytes字节数多,则消息就会截短到nbytes字节,且不通知消息发送进程
IPC_NOWAIT:调用进程会立即返回。若没有收到消息则立即返回 -1
注意:
1、若消息队列中有多种类型的消息,msgrcv获取消息的时候按消息类型获取,不是先进先出的。
2、在获取某类型消息的时候,若队列中有多条此类型的消息,则获取最先添加的消息,即先进先出原则。
- 返回值:
- 成功:返回读取消息的长度
- 失败:返回 -1
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
long msg_type; // 消息类型,必须在结构体的第一个位置并且类型必须是long
char msg_text[N]; // 消息正文
}MSG;
#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过msgget函数创建一个消息队列
int msqid1;
if((msqid1 = msgget(mykey, IPC_CREAT | 0666)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid1 = %d\n", msqid1);
system("ipcs -q");
// 通过msgrcv函数接收消息队列中的信息(读操作)
MSG msg;
// if(msgrcv(msqid1, &msg, MSGTEXT_SIZE, 0, 0) == -1)
// 如果没有第四个参数指定的消息时,msgrcv函数会阻塞等待
if(msgrcv(msqid1, &msg, MSGTEXT_SIZE, 2, 0) == -1)
{
perror("fail to msgrcv");
exit(1);
}
printf("recv_msg = %s\n", msg.msg_text);
system("ipcs -q");
return 0;
}
消息队列的控制
消息队列的控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 功能:对消息队列进行各种控制,如修改消息队列的属性,或删除消息队列
- 参数
- msqid:消息队列的标识符
- cmd:函数功能的控制
- buf:msqid_ds数据类型的地址,用来存放或更改消息队列的属性
cmd:函数功能的控制
1、IPC_RMID:删除由msqid指示的消息队列,将它从系统中删除并破坏相关数据结构
2、IPC_STAT:将msqid相关的数据结构中各个元素的当前值存入到由buf指向的结构中
3、IPC_SET:将msqid相关的数据结构中的元素设置为由buf指向的结构中的对应值
- 返回值:成功返回0,失败返回 -1
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
long msg_type; // 消息类型,必须在结构体的第一个位置并且类型必须是long
char msg_text[N]; // 消息正文
}MSG;
#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过msgget函数创建一个消息队列
int msqid1;
if((msqid1 = msgget(mykey, IPC_CREAT | 0666)) == -1)
{
perror("fail to msgget");
exit(1);
}
printf("msqid1 = %d\n", msqid1);
system("ipcs -q");
// 通过msgctl函数删除消息队列
if(msgctl(msqid1, IPC_RMID, NULL) == -1)
{
perror("fail to msgctl");
exit(1);
}
system("ipcs -q");
return 0;
}
共享内存
共享内存:允许两个或多个进程共享给定的存储区域
共享内存的特点:
1、共享内存是进程间共享数据的一种最快的方法
一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容
2、使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥
若一个进程正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数据
获得一个共享存储标识符-shmget
获得一个共享存储标识符-shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
使用shell命令操作共享内存
查看共享内存
ipcs -m
删除共享内存
ipcrm -m shmid
共享内存映射(attach)
解除共享内存映射(detach)
使用共享内存实现读写操作
使用共享内存实现读写操作
write案例,写char *类型数据
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过shmget函数创建或打开一个共享内存,返回一个共享内存的标识符
int shmid;
if((shmid = shmget(mykey, 500, IPC_CREAT | 0666)) == -1)
{
perror("fail to shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs -m");
// 使用shmat函数映射共享内存的地址
char *text;
if((text = shmat(shmid, NULL, 0)) == (void *)-1)
{
perror("fail to shmat");
exit(1);
}
// 通过shmat的返回值对共享内存操作
strcpy(text, "hello world");
// 操作完毕后要解除共享内存的映射
if(shmdt(text) == -1)
{
perror("fail to shmdt");
exit(1);
}
return 0;
}
read案例,读char *类型数据
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过shmget函数创建或打开一个共享内存,返回一个共享内存的标识符
int shmid;
if((shmid = shmget(mykey, 500, IPC_CREAT | 0666)) == -1)
{
perror("fail to shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs -m");
// 使用shmat函数映射共享内存的地址
char *text;
if((text = shmat(shmid, NULL, 0)) == (void *)-1)
{
perror("fail to shmat");
exit(1);
}
// 通过shmat的返回值, 获取共享内存中的数据
printf("text = %s\n", text);
// 操作完毕后要解除共享内存的映射
if(shmdt(text) == -1)
{
perror("fail to shmdt");
exit(1);
}
system("ipcs -m");
return 0;
}
案例2
写和读 结构体类型数据
写
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#include <sys/types.h>
#include <string.h>
typedef struct{
int a;
char b;
}MSG;
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过shmget函数创建或打开一个共享内存,返回一个共享内存的标识符
int shmid;
if((shmid = shmget(mykey, 500, IPC_CREAT | 0666)) == -1)
{
perror("fail to shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs -m");
// 使用shmat函数映射共享内存的地址
// char *text;
MSG *text;
if((text = shmat(shmid, NULL, 0)) == (void *)-1)
{
perror("fail to shmat");
exit(1);
}
// 通过shmat的返回值对共享内存操作
// strcpy(text, "hello world");
text->a = 100;
text->b = 'w';
// 操作完毕后要解除共享内存的映射
if(shmdt(text) == -1)
{
perror("fail to shmdt");
exit(1);
}
system("ipcs -m");
return 0;
}
读
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#include <sys/types.h>
#include <string.h>
typedef struct{
int a;
char b;
}MSG;
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过shmget函数创建或打开一个共享内存,返回一个共享内存的标识符
int shmid;
if((shmid = shmget(mykey, 500, IPC_CREAT | 0666)) == -1)
{
perror("fail to shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs -m");
// 使用shmat函数映射共享内存的地址
// char *text;
MSG *text;
if((text = shmat(shmid, NULL, 0)) == (void *)-1)
{
perror("fail to shmat");
exit(1);
}
// 通过shmat的返回值, 获取共享内存中的数据
// printf("text = %s\n", text);
printf("a = %d, b = %c\n", text->a, text->b);
// 操作完毕后要解除共享内存的映射
if(shmdt(text) == -1)
{
perror("fail to shmdt");
exit(1);
}
system("ipcs -m");
return 0;
}
共享内存控制-shmctl
共享内存控制-shmctl
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{
// 使用ftok函数获取键值
key_t mykey;
if((mykey = ftok(".", 100)) == -1)
{
perror("fail to ftok");
exit(1);
}
// 通过shmget函数创建或打开一个共享内存,返回一个共享内存的标识符
int shmid;
if((shmid = shmget(mykey, 500, IPC_CREAT | 0666)) == -1)
{
perror("fail to shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
system("ipcs -m");
// 通过shmctl函数删除共享内存
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("fail to shmctl");
exit(1);
}
system("ipcs -m");
return 0;
}
线程
线程概念
1、每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进程创建、切换、撤销操作时,需要较大的系统开销。
2、为了减少系统开销,从进程中演化出了线程
3、线程存在于进程中,共享进程的资源
4、线程是进程中的独立控制流,由环境(包括寄存器组和程序计数器)和一系列的执行指令组成。
5、每个进程都有一个地址空间和一个控制线程
多线程的用途
- 多任务程序的设计
- 一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法
- 并发程序设计
- 一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同任务步骤建立线程
- 网络程序设计
- 为提供网络的利用效率,我们可能使用多线程,对每个连接用一个线程去处理
- 数据共享
- 同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享
在多CPU系统中,实现真正的并行。
线程的创建
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 功能:
- 创建一个线程
- 参数
- thread:线程标识符地址
- attr:线程属性结构体地址
- start_routine:线程函数的入口地址
- arg:传给线程函数的参数
- 返回值
- 成功:返回0
- 失败:返回非0
1、与fork不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指定的函数开始运行,该函数运行完后,该线程也就退出了
2、线程依赖进程存在,如果创建线程的进程结束了,线程也就结束了
3、线程函数的程序在pthread库中,故链接时要加上参数-lpthread
注意:由于线程库原本不是系统本身的,所以在链接时需要手动链接库文件 gcc *.c -lpthread
出错的
正确的
1、通过pthread_create函数创建子线程
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 由于线程库原本不是系统本身的,所以在链接时需要手动链接库文件 gcc *.c -lpthread
void *thread_fun(void *arg)
{
printf("子线程正在运行\n");
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 由于进程结束后,进程中所有的线程都会强制退出,所以现阶段使用while不要让进程退出
while (1);
return 0;
}
2、一个进程中的多个线程执行顺序是不确定的,没有先后顺序可言
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
// 一个进程中的多个线程执行顺序是不确定的,没有先后顺序可言
// 多线程执行时跟进程一样,是来回切换运行的,跟进程的调度机制一样
void *thread_fun(void *arg)
{
printf("子线程1正在运行\n");
sleep(1);
printf("**************************\n");
}
void *thread_fun2(void *arg)
{
printf("子线程2正在运行\n");
sleep(1);
printf("------------------------\n");
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1,thread2;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过pthread_create函数创建子线程
if(pthread_create(&thread2, NULL, thread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
while (1);
return 0;
}
3、线程、传参
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
int num =100;
void *thread_fun(void *arg)
{
printf("子线程1正在运行 num = %d\n",num);
num++;
int n = *(int *)arg;
printf("1 n = %d\n", n);
*(int *)arg = 111;
}
void *thread_fun2(void *arg)
{
printf("子线程2正在运行 num = %d\n",num);
sleep(1);
int n = *(int *)arg;
printf("2 n = %d\n", n);
}
int main()
{
int a = 666;
printf("主控线程正在执行\n");
pthread_t thread1,thread2;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, (void *)&a ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过pthread_create函数创建子线程
if(pthread_create(&thread2, NULL, thread_fun2, (void *)&a) != 0)
{
perror("fail to pthread_create");
exit(1);
}
while (1);
return 0;
}
线程等待
线程等待
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
static int num = 666;
printf("子线程1正在运行\n");
sleep(3);
printf("子线程要退出了\n");
// 子线程如果要返回退出状态,可以通过返回值或者通过pthread_exit函数
return (void *)#
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
int *num;
// 通过调用pthread_join函数阻塞等待子线程退出
// if(pthread_join(thread1, NULL) != 0)
if(pthread_join(thread1, (void **)&num ) != 0)
{
perror("fail to pthread_join");
exit(1);
}
printf("ret_val = %d\n", *num);
printf("进程要退出了\n");
return 0;
}
线程分离
线程分离
线程的结合态和分离态
Linux线程执行和windows不同,pthread有两种状态:
1、可结合的(joinable)或者是分离的(detached),线程默认创建为可结合态。
2、如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放
3、若是detached状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放,使用pthread_detach函数将线程设置为分离态。
创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了线程分离函数:pthread_detach。
pthread_detach
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("子线程1正在运行\n");
sleep(3);
printf("子线程要退出了\n");
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过pthread_detach函数将子线程设置为分离态,既不用阻塞,也可以自动回收子线程退出的资源
if(pthread_detach(thread1) != 0)
{
perror("fail to pthread_detach");
exit(1);
}
// 通过调用pthread_join函数阻塞等待子线程退出
// 如果原本子线程是结合态,需要通过pthread_join函数回收子线程退出的资源
// 但是这个函数是一个阻塞函数,如果子线程不退出,就会导致当前进程(主控线程)无法继续执行,大大的限制了代码的运行效率
// 如果子线程已经设置为分离态,就不需要再使用pthread_join了
#if 0
if(pthread_join(thread1, NULL) != 0)
{
perror("fail to pthread_join");
exit(1);
}
#endif
while (1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
线程退出
线程退出
在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流
1、线程从执行函数中返回
2、线程调用pthread_exit退出进程
3、线程可以被同一进程中的其它线程取消
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
printf("子线程1正在运行\n");
static char buf[] = "This thread has quited";
int i;
for(i = 0; i<10; i++)
{
if(i == 5)
{
// 通过pthread_exit函数退出当前线程
// pthread_exit(NULL);
pthread_exit(buf);
}
printf("*********\n");
sleep(1);
}
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// pthread_join(thread1, NULL);
char *str;
pthread_join(thread1, (void **)&str);
printf("str = %s\n",str);
printf("进程要退出了\n");
return 0;
}
线程的取消pthread_cancel
线程的取消
取消线程是指取消一个正在执行线程的操作
案例1
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
while (1)
{
printf("子线程1正在运行\n");
sleep(1);
}
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过调用pthread_cancel函数取消另一个线程
sleep(3);
pthread_cancel(thread1);
pthread_join(thread1, NULL);
printf("进程要退出了\n");
return 0;
}
#include <pthread.h>
1、int pthread_setcancelstate(int state, int *oldstate);
2、int pthread_setcanceltype(int type, int *oldtype);
3、void pthread_testcancel(void);
pthread_setcancelstate() ==> 线程的取消状态
在Linux系统下,线程默认可以被取消。编程时可以通过pthread_setcancelstate函数设置线程是否可以被取消。
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
// 通过pthread_setcancelstate设置取消的状态
// 设置为可以取消
// pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置为不可取消
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
while (1)
{
printf("子线程1正在运行\n");
sleep(1);
}
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过调用pthread_cancel函数取消另一个线程
sleep(3);
pthread_cancel(thread1);
pthread_join(thread1, NULL);
printf("进程要退出了\n");
return 0;
}
pthread_testcancel ⇒ 线程的取消点
线程被取消后,该线程并不是马上终止,默认情况下线程执行到取消点时才能被终止。编程时可以通过pthread_testcancel函数设置线程的取消点。
void pthread_testcancel(void);
当别的线程取消调用此函数的线程时候,被取消的线程执行到此函数时结束。
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
while (1)
{
printf("子线程1正在运行\n");
sleep(1);
pthread_testcancel();
}
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过调用pthread_cancel函数取消另一个线程
sleep(3);
pthread_cancel(thread1);
pthread_join(thread1, NULL);
printf("进程要退出了\n");
return 0;
}
pthread_setcanceltype ==> 线程取消类型(是否可以被立即取消)
线程被取消后,该线程并不是马上终止,默认情况下线程执行到取消点时才能被终止。编程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置线程取消的类型 设置为立即取消
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// 设置为不立即取消
while (1)
{
printf("子线程1正在运行\n");
sleep(1);
pthread_testcancel();
}
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, thread_fun, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
// 通过调用pthread_cancel函数取消另一个线程
sleep(3);
pthread_cancel(thread1);
pthread_join(thread1, NULL);
printf("进程要退出了\n");
return 0;
}
线程退出清理函数
和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程清理处理程序(threadcleanup handler)
注意:
1、线程可以建立多个清理处理程序
2、处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
当线程执行以下动作时会调用清理函数:
1、调用pthread_exit退出线程
2、响应其他线程的取消请求
3、用非零execute调用pthread_cleanup_pop
无论哪种情况 pthread_cleanup_pop 都将删除上一次pthread_cleanup_push调用注册的清理处理函数。
-
注册清理函数pthread_cleanup_push
-
弹出清理函数pthread_cleanup_pop
案例:验证线程调用pthread_exit函数时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
#include<string.h>
void mycleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
/* 建立线程清理程序 */
printf("this is new thread\n");
char *ptr = NULL;
ptr = (char *)malloc(100); // 堆区开辟空间
pthread_cleanup_push(mycleanup, (void*)(ptr));
bzero(ptr, 100); // 清空字符串 保证里面没有其他内容
strcpy(ptr, "memory from malloc");
sleep(3);
printf("before exit\n");
pthread_exit(NULL);
/* 注意push与pop必须配对使用,即使pop执行不到 */
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main()
{
printf("主控线程正在执行\n");
pthread_t tid;
// 通过pthread_create函数创建子线程
if(pthread_create(&tid, NULL, thread, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(tid, NULL);
printf("process is dying\n");
return 0;
}
案例:验证线程被取消时,系统自动调用线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
#include<string.h>
void mycleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
/* 建立线程清理程序 */
printf("this is new thread\n");
char *ptr = NULL;
ptr = (char *)malloc(100); // 堆区开辟空间
pthread_cleanup_push(mycleanup, (void*)(ptr));
bzero(ptr, 100); // 清空字符串 保证里面没有其他内容
strcpy(ptr, "memory from malloc");
sleep(10);
/* 注意push与pop必须配对使用,即使pop执行不到 */
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main()
{
printf("主控线程正在执行\n");
pthread_t tid;
// 通过pthread_create函数创建子线程
if(pthread_create(&tid, NULL, thread, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
sleep(5);
printf("before cancel\n");
/* 子线程响应pthread_cancel后,会执行线程处理函数 */
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("process is dying\n");
return 0;
}
案例:验证调用pthread_cleanup_pop函数时,系统自动调用线程清理函数
多个清理函数的情况下,处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
#include<string.h>
void cleanup_fun1(void *arg)
{
printf("in cleanup func1\n");
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void cleanup_fun2(void *arg)
{
printf("in cleanup func2\n");
}
void *thread(void *arg)
{
/* 建立线程清理程序 */
printf("this is new thread\n");
char *ptr = NULL;
ptr = (char *)malloc(100); // 堆区开辟空间
pthread_cleanup_push(cleanup_fun1, (void*)(ptr));
pthread_cleanup_push(cleanup_fun2, NULL);
bzero(ptr, 100); // 清空字符串 保证里面没有其他内容
strcpy(ptr, "memory from malloc");
sleep(3);
/* 注意push与pop必须配对使用,即使pop执行不到 */
printf("before pop\n");
pthread_cleanup_pop(1);
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main()
{
printf("主控线程正在执行\n");
pthread_t tid;
// 通过pthread_create函数创建子线程
if(pthread_create(&tid, NULL, thread, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(tid, NULL);
printf("process is dying\n");
return 0;
}
多任务互斥和同步
多任务互斥和同步
1、在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源
2、多个任务之前有依赖关系,某个任务的运行依赖于另一个任务
同步和互斥就是用于解决上述这两个问题的。
- 互斥:
- 一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。POSIX标准中进程和现场同步和互斥的方法,主要有信号量和互斥锁两种方式。
- 同步:
- 两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。
同步就是在互斥的基础上有顺序
互斥锁
互斥锁
1、mutex是一种简单的加锁方法来控制对共享资源的访问,mutex只有两种状态,即上锁(lock)和解锁(unlock)。
2、在访问该资源前,首先应申请mutex,如果mutex处于unlock状态,则会申请到mutex并立即lock;如果mutex处于lock状态,则默认阻塞申请者。
3、unlock操作应该由lock者进行。
头文件
#include <pthread.h>
销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
1、功能:销毁指定的一个互斥锁。
2、参数:mutex => 互斥锁地址
3、返回值:成功返回0,失败返回非0
初始化互斥锁
1、int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
功能:初始化一个互斥锁
返回值:成功0参数:
mutex:指定的互斥锁
mutexattr:互斥锁的属性,为NULL表示默认属性
2、pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
互斥锁上锁1
int pthread_mutex_lock(pthread_mutex_t *mutex);
1、功能:对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁。
2、参数:mutex => 互斥锁地址
3、返回值:成功返回0,失败返回非0
互斥锁上锁2
int pthread_mutex_trylock(pthread_mutex_t *mutex);
1、功能:对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回。
2、参数:mutex => 互斥锁地址
3、返回值:成功返回0,失败返回非0
互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
1、功能:对指定的互斥锁解锁。
2、参数:mutex => 互斥锁地址
3、返回值:成功返回0,失败返回非0
案例:不使用互斥锁
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
#include<string.h>
int money = 10000;
void *pthread_fun1(void *arg)
{
int get, yu, shiji;
get = 10000;
printf("张三正在查询余额...\n");
sleep(1);
yu = money;
printf("张三正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("张三想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
int get, yu, shiji;
get = 10000;
printf("李四正在查询余额...\n");
sleep(1);
yu = money;
printf("李四正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("李四想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
pthread_exit(NULL);
}
int main()
{
printf("主控线程正在执行\n");
pthread_t thread1, thread2;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, pthread_fun1, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("主控线程:process is dying\n");
return 0;
}
案例:使用互斥锁
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <pthread.h>
#include<string.h>
int money = 10000;
// 通过互斥锁解决线程间互斥问题
// 第一步:创建互斥锁(由于两个线程操作同一个互斥锁,所以定义在全局更加方便一点)
pthread_mutex_t mymutex;
void *pthread_fun1(void *arg)
{
int get, yu, shiji;
get = 10000;
// 第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mymutex);
printf("张三正在查询余额...\n");
sleep(1);
yu = money;
printf("张三正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("张三想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
// 第四步:当共享资源的操作执行完毕后,对互斥锁执行解锁操作
pthread_mutex_unlock(&mymutex);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
int get, yu, shiji;
get = 10000;
// 第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mymutex);
printf("李四正在查询余额...\n");
sleep(1);
yu = money;
printf("李四正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("李四想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
// 第四步:当共享资源的操作执行完毕后,对互斥锁执行解锁操作
pthread_mutex_unlock(&mymutex);
pthread_exit(NULL);
}
int main()
{
// 第二步:初始化互斥锁
pthread_mutex_init(&mymutex, NULL);
printf("主控线程正在执行\n");
pthread_t thread1, thread2;
// 通过pthread_create函数创建子线程
if(pthread_create(&thread1, NULL, pthread_fun1, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 第五步:当互斥锁使用完毕后,要销毁
pthread_mutex_destroy(&mymutex);
printf("主控线程:process is dying\n");
return 0;
}
信号量
信号量
1、信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
2、编程时可根据操作信号量的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞。
1、P V 原语是对信号量的操作,一次P操作使信号量sem减1,一次V操作使信号量sem加1。
2、对于P操作,如果信号量的sem值为小于等于0,则P操作就会堵塞,如果信号量的值大于0,才可以执行P操作进行减1
信号量主要用于进程或线程间的同步和互斥这两种典型情况。
1、若用于互斥,几个进程(或线程)往往只设置一个信号量
2、若用于同步操作,往往设置多个信号量,并且安排不同的初始值,来实现它们之间的执行顺序。
信号量用于互斥
信号量用于同步
信号量的初始化
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
信号量P操作
#include <semaphore.h>
int sem_wait(sem_t *sem);
#include <semaphore.h>
int sem_trywait(sem_t *sem);
信号量V操作
#include <semaphore.h>
int sem_post(sem_t *sem);
获取信号量的计数器
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
信号量的销毁
#include <semaphore.h>
int sem_destroy(sem_t *sem);
信号量实现互斥功能
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <semaphore.h>
#include<string.h>
// 通过信号量实现互斥操作
// 第一步:创建一个信号量
sem_t sem;
void printer(char *str)
{
// 第三步:执行P操作
// 由于使用信号量实现互斥,信号量的初始值设置为1,则两个线程执行P操作
// 先执行P操作的线程继续执行,后执行P操作的阻塞等待着
sem_wait(&sem);
while (*str)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
// 第四步:执行V操作
sem_post(&sem);
}
void *thread_fun1(void *arg)
{
char *str1 = "hello";
printer(str1);
}
void *thread_fun2(void *arg)
{
char *str2 = "world";
printer(str2);
}
int main()
{
// 第二步:初始化信号量
sem_init(&sem, 0, 1);
printf("主控线程正在执行\n");
pthread_t tid1, tid2;
// 通过pthread_create函数创建子线程
if(pthread_create(&tid1, NULL, thread_fun1, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&tid2, NULL, thread_fun2, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("\n");
printf("主控线程:process is dying\n");
// 第五步:使用完毕后销毁信号量
sem_destroy(&sem);
return 0;
}
信号量实现同步功能
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
#include <semaphore.h>
#include<string.h>
// 通过信号量实现同步功能,如果两个线程实现同步,需要通过两个信号量
char ch = 'a';
// 第一步:创建两个信号量
sem_t sem_g, sem_p;
void * pthread_g(void *arg)
{
while (1)
{
// 第四步:后执行的线程中,信号量的初始值为0的信号量,执行P操作
sem_wait(&sem_g);
ch++;
sleep(1);
// 第六步:后执行的线程执行完毕后,信号量初始值为1的信号量,执行V操作
sem_post(&sem_p);
}
}
void *pthread_p(void *arg) // 此线程打印ch的值
{
while (1)
{
// 第三步:先执行的线程中,信号量初始值为1的信号量,执行P操作
sem_wait(&sem_p);
printf("%c",ch);
fflush(stdout);
// 第五步:当先执行的线程执行完毕后,信号量初始值为0的信号量,执行V操作
sem_post(&sem_g);
}
}
int main()
{
// 第二步:初始化信号量
sem_init(&sem_g, 0, 0);
sem_init(&sem_p, 0, 1);
printf("主控线程正在执行\n");
pthread_t tid1, tid2;
// 通过pthread_create函数创建子线程
if(pthread_create(&tid1, NULL, pthread_g, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&tid2, NULL, pthread_p, NULL ) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("\n");
printf("主控线程:process is dying\n");
// 第七步:使用完毕后销毁信号量
sem_destroy(&sem_g);
sem_destroy(&sem_p);
return 0;
}