- 1、程序进程线程的基本概念
程序:源代码、指令(静态的),一个程序中可以有多个进程。
进程:正在执行的程序实例(运行,动态的),不同进程之间是相互独立的(拥有不同的内存空间),进程使用完后linux内核会回收进程资源。
线程:由进程引发,线程从属于进程。这是因为创建进程与回收进程资源会消耗大量资源,故产生了线程。一个进程可以有多个线程,多个线程共享这一个线程的资源(比如线程中的变量)。
任务:具体要做的事情(由进程和线程做)。
- 2、查询正在运行的进程PID
函数:pstree -g 可以查询进程树
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //返回正在调用的进程PID
pid_t getppid(void); //返回正在调用的进程的父进程的PID
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main()
{
pid_t pid;
while(1)
{
cout << "pid = " << getpid() << endl;
cout << "ppid = " << getppid () << endl;
cout << "hello world" << endl;
sleep(1);
}
return 0;
}
- 3、创建一个子进程
函数:通过拷贝调用进程(父进程),创建一个子进程,子进程中包含父进程中所用代码,并且会单独分配内存空间。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void); //父进程将返回子进程的PID,子进程将返回0
同时创建多个进程 ,下面例子将产生四个进程。他们的代码均相同,都会输出cout中的内容。
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main()
{
pid_t pid1, pid2;
pid1 = fork();
pid2 = fork();
cout << "pid1 = " << pid1 << " pid2 = " << pid2 << endl;
cout << "getppid() = " << getppid() << endl;
// cout << "Hello world" << endl;
return 0;
}
/*
pid1 = 308770 pid2 = 308771
getppid() = 302678
pid1 = 308770 pid2 = 0
getppid() = 308769
pid1 = 0 pid2 = 308772
pid1 = 0 pid2 = 0
getppid() = 308769
getppid() = 308770
*/
一个进程中创建的子进程他们内容相同,但拥有不同的内存地址,故全局变量count各有一份,互不干扰。
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int count = 0;
int main()
{
pid_t pid;
pid = fork();
if(pid > 0) // parent process
{
while (1)
{
// std::cout << "Hello world" << std::endl;
std::cout << "parent process" << "count = " << count ++ << std::endl;
sleep(1);
}
}
else if (pid == 0) //child process
{
while (1)
{
std::cout << "child process count = " << count ++ << std::endl;
sleep(2);
}
}
else
std::cout << "PROCESS ERROR!" << std::endl;
return 0;
}
/*结果
parent processcount = 0
child process count = 0
parent processcount = 1
parent processcount = 2
child process count = 1
parent processcount = 3
child process count = 2
parent processcount = 4
parent processcount = 5
child process count = 3
parent processcount = 6
*/
父进程结束不影响子进程,下面例子即父进程输出10次结束,而子进程仍然在继续运行。
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int count = 0;
int main()
{
pid_t pid;
int i = 0;
pid = fork();
if(pid > 0) // parent process
{
for(i = 0; i < 10; i++)
{
// std::cout << "Hello world" << std::endl;
std::cout << "parent process " << "count = " << count ++ << std::endl;
sleep(1);
}
}
else if (pid == 0) //child process
{
while (1)
{
std::cout << "child process count = " << count ++ << std::endl;
sleep(1);
}
}
else
std::cout << "PROCESS ERROR!" << std::endl;
return 0;
}
为什么要用到多个进程?
(1)执行不同的任务。
父进程与子进程宏观上为并行运行(cout看不出来谁先运行),默认是父进程先调用,几乎可以忽略不计
(2)父进程与子进程具有不同的内存空间,同名变量(不论是否为全局变量),并不是同一个地址,故改变不会相互影响。
(3)父进程结束,子进程不一定结束,互不干扰。
- 4、检测子进程
wait函数将一直等待子进程结束,若没有监测到进程结束,则将一直阻塞。
#include <sys/types.h>
#include <sys/wait.h>
/*
监测子进程是否结束,返回值均为结束的PID,可定义一个整形变量储存
进程结束后返回的状态。
*/
pid_t wait(int *wstatus);
/*
监测指定子进程是否结束,返回值均为结束的PID,可定义一个整形变量储存
进程结束后返回的状态。
*/
pid_t waitpid(pid_t pid, int *wstatus, int options);
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
using namespace std;
//run model: ./a.out 10 5
int main(int argc, char * argv[])
{
pid_t child_pid;
int numDead;
int i;
for(i = 1; i < argc; i ++)
{
switch (fork())
{
case -1:
perror("fork()");
exit(0);
case 0:
cout << "Child " << i << "started ID = " << getpid() << "sleeping " << argv[i] << " sec." <<endl;
sleep(atoi(argv[i]));
exit(0);
default:
break;
}
}
numDead = 0;
while (1)
{
child_pid = wait(nullptr);
numDead ++;
cout << "wait() returned child PID : " << child_pid << " numDead = " << numDead << endl;
if(child_pid == -1)
{
cout << "No more children, Byebye!" << endl;
exit(0);
}
}
return 0;
}
- 5、线程
单个线程的创建:
由进程创建,并且共享进程资源。同一个进程创建的多个进程,也将共享同一片内存单元的资源,多个线程几乎并发运行。
创建线程函数如下:
/*
第一个参数表示线程ID号,输出的。第二个为,一个结构体指针,存放初始化线程属性。
第三个参数,函数指针,函数指针名为void * (*start_routine)(void *) 返回值为void *
传入参数为void * 可以为任意数据;start_roution()函数为新线程所要执行的内容。第四个为传入start_roution()中的参数
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
param: pthread_t thread 线程的ID
void **retval 保存目标返回的状态
*/
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
//Compile and link with -pthread.
线程例子如下:
创建两个线程变量,pthread。使用pthread_create()创建线程,并创建线程的执行函数。进程自动调用进程函数thread_function(),通过传入参数count,执行五次循环,打印输出Hello world。最后调用pthread_join()结束进程。
void * thread_function(void *);
int main()
{
pthread_t pthread;
int ret;
int count = 5;
ret = pthread_create(&pthread, nullptr, thread_function,&count);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
pthread_join(pthread,nullptr);
std::cout << "The thread is over, process over too " << std::endl;
return 0;
}
void * thread_function(void *arg)
{
int i;
std::cout << "Thread begins running" << std::endl;
for(i = 0; i < * static_cast<int *>(arg); i ++)
{
std::cout << "Hello world" << std::endl;
sleep(1);
}
return nullptr;
}
多个进程的创建:
创建pthread1, 与pthread2线程变量,调用pthread_create创建进程,创建成功则返回零。并且创建全局变量,分别在两个线程函数中将变量后++,观察输出发现,两个线程函数共享进程资源,两个线程均使用同一个count变量。
void * thread1_function(void *arg);
void * thread2_function(void *arg);
int count = 0;
int main()
{
pthread_t pthread1, pthread2;
int ret;
// int count = 5;
ret = pthread_create(&pthread1, nullptr, thread1_function,nullptr);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
ret = pthread_create(&pthread2, nullptr, thread2_function, nullptr);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
pthread_join(pthread1,nullptr);
pthread_join(pthread2,nullptr);
std::cout << "The thread is over, process over too " << std::endl;
return 0;
}
void * thread1_function(void *arg)
{
std::cout << "Thread begins running" << std::endl;
while(1)
{
// std::cout << "Hello world" << std::endl;
std::cout << "pthread1 count = " << count ++ << std::endl;
sleep(1);
}
return nullptr;
}
void * thread2_function(void *arg)
{
std::cout << "Thread2 begins running" << std::endl;
while(1)
{
// std::cout << "Good morning" << std::endl;
std::cout << "pthread2 count = " << count ++ << std::endl;
sleep(1);
}
return nullptr;
}
- 6、无名管道
实现任务间的通信,同步(只适用于亲缘关系的进程)
进程间通信:管道(pipe):分为无名管道和有名管道,无名管道只能单向传输信息,管道两端都有读写两端,使用时,一端打开读端,一端打开写端,进行单向传输。设计管道参考思路,父进程,可以打开写端,对于子进程打开读端,实现由父进程向子进程传输消息的单向通信。如果需要实现父子进程的双向通信,则需要两条管道。而有名管道可以实现没有亲缘关系的进程之间的通信。即两个不同cpp,编译成不同的可执行文件,两个不同的可执行文件(两个进程)通信。
创建管道函数:
/*
param : 创建一个管道,pipefd 将返回创建的管道文件描述符,pipefd[0]
指向管道的读端,pipefd[1]指向管道的写端。管道的数据储存在内核中,若没有读取
数据将阻塞在内核中。
return: 创建成功返回0, 失败返回-1, 并设置appropriately
*/
int pipe2(int pipefd[2], int flags);
int pipe(int pipefd[2]);
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
int main()
{
int fd[2];
pid_t pid;
if(pipe(fd) == -1)
{
perror("pipe");
}
pid = fork();
if(pid > 0) //parent process
{
close(fd[0]);
sleep(5);
write(fd[1], "ab", 2);
while (1);
}
else if(pid == 0)
{
char ch[2];
std::cout << "Child process is waiting for data: " << std::endl;
close(fd[1]);
read(fd[0], ch, 2); //没有收到消息,将会一直等待阻塞
std::cout << "Read from pipe: " << ch << std::endl;
}
return 0;
}
一个管道的容量:
子进程一直向管道写内容,每次一个字节,并用count计数,记录写的次数,而父进程监控子进程结束。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int fd[2];
if(pipe(fd) != 0)
perror("pipe");
pid = fork();
if(pid == 0)
{
int count = 0;
char ch = '*';
close(fd[0]);
while(1)
{
write(fd[1], &ch, 1);
std::cout << "count = " << ++count << std::endl;
}
}
else if(pid > 0)
{
waitpid(pid, nullptr, 0);
}
}
父子进程之间的管道通信。子进程关闭管道的读端,键盘输入内容向管道中写。父进程关闭写端,向管道中读取内容。若没有读取到内容,则将一直堵塞。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int fd[2];
if(pipe(fd) != 0)
perror("pipe");
pid = fork();
if(pid == 0)
{
char temp[100];
close(fd[0]);
while(1)
{
std::cin >> temp;
write(fd[1], temp, sizeof(temp));
}
}
else if(pid > 0)
{
close(fd[1]);
char temp[100];
while(1)
{
read(fd[0], temp, sizeof(temp));
std::cout << "recevie data = " << temp << std::endl;
}
}
}
由于管道是单向通信,若要通过管道实现父子进程间的双向通信,则需要两条管道。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <string.h>
int main()
{
pid_t pid;
int fd1[2],fd2[2];
if(pipe(fd1) != 0)
perror("pipe1");
if(pipe(fd2) != 0)
perror("pipe2");
pid = fork();
if(pid == 0)
{
char temp[100];
close(fd1[1]);
close(fd2[0]);
while(1)
{
memset(temp, '\0', sizeof(temp));
read(fd1[0],temp, sizeof(temp));
printf("Parent send temp = %s \n", temp);
for(int i = 0; i < sizeof(temp); i ++)
temp[i] = toupper(temp[i]);
write(fd2[1], temp, sizeof(temp));
}
}
else if(pid > 0)
{
close(fd1[0]);
close(fd2[1]);
char temp[100];
while(1)
{
memset(temp, '\0', sizeof(temp));
std::cin.getline(temp, sizeof(temp));
write(fd1[1], temp, sizeof(temp));
memset(temp, '\0', sizeof(temp));
read(fd2[0], temp, sizeof(temp));
std::cout << "Child send temp = " << temp << std::endl;
}
}
}
有名管道,可以实现两个没有亲缘关系进程之间的通信。
读端:
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
using namespace std;
/*
param: 文件路径
mode 设置文件模式,读写;
创建一个先入先出的文件,被叫做有名管道。一旦创建这个文件,
任何进程都可以打开这个管道进行读写,同操作文件的读写一样,
但读的时候,必须等待写端被打开,否则会阻塞。
return: 成功返回0
int mkfifo(const char * pathname, mode_t mode)
*/
int main()
{
int ret, fd;
char buf[100];
ret = mkfifo("my_fifo", 666);//命名管道具有读写权限
if(ret != 0)
std::cout << "mkfifo" << std::endl;
cout << "Prepare reading from named pipe: " << endl;
fd = open("my_fifo", O_RDWR);
if(fd == -1)
std::cout << "open" << std::endl;
while(1)
{
memset(buf, '\0', sizeof(buf));
read(fd, buf, sizeof(buf));
std::cout << "Read from named pipe: " << '\n'
<< buf << std::endl;
sleep(10);
}
return 0;
}
写端:
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
using namespace std;
/*
param: 文件路径
mode 设置文件模式,读写;
创建一个先入先出的文件,被叫做有名管道。一旦创建这个文件,
任何进程都可以打开这个管道进行读写,同操作文件的读写一样,
但读的时候,必须等待写端被打开,否则会阻塞。
return: 成功返回0
int mkfifo(const char * pathname, mode_t mode)
*/
int main(int argc, char *argv[])
{
int fd;
char buf[100];
fd = open("my_fifo", O_WRONLY); //只允许写
if(fd == -1)
std::cout << "open" << std::endl;
if(argc == 1)
{
printf("Please send something to named pipe: \n");
exit(EXIT_FAILURE);
}
strcpy(buf,argv[1]);
write(fd, buf, sizeof(buf));
printf("Write to the pipe: %s\n", buf);
return 0;
}
- 7、共享内存
开辟一个共享的内存空间,父进程与子进程不相关的均可以看到这块内存,从而实现进程之间的通信。创建共享内存的步骤如下:
(1)创建共享内存
(2)映射共享内存到线程(父子进程均需要映射)
(3)解除映射
(4)删除共享内存
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
/*
开辟System V 的共享内存单元
return : 返回System V共享内存单元的描述符,与Key相关联,Key是内核中使用。
当key值为IPC_PRIVATE则创建size大小的共享内存,
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
将描述的共享内存空间映射到调用的进程
param:1、共享内存空间的描述符
2、若为空,则自动选择当前进程映射的地址
3、赋予进程对共享内存的读写权限,为0,则为读写都具备。
return: 若成功,则返回映射到内存空间的首地址。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
解除绑定,
int shmdt(const void *shmaddr);
*/
char msg[] = "Hello world";
int main()
{
int shmid;
pid_t pid;
shmid = shmget(IPC_PRIVATE, 1024, 666|IPC_CREAT);
pid = fork();
if(pid > 0)
{
char * p_addr;
p_addr = shmat(shmid, NULL, 0);
memset(p_addr, '\0', sizeof(msg));
memcpy(p_addr, msg, sizeof(msg));
shmdt(p_addr);
waitpid(pid, NULL, 0);
}
else if(pid == 0)
{
char *c_addr;
c_addr = (shmat(shmid, NULL, 0));
printf("Child process waits a short time: \n");
sleep(3);
printf("Chiled process reads from shared memory: %s\n", c_addr);
shmdt(c_addr);
}
else
{
printf("fork() ");
}
return 0;
}
非亲缘关系的进程,若想使用共享内存,则需要进行需要自定义键值。IPC_PRIVATE由系统自动生成键值。
read读端
#define MY_KEY 9527
int main()
{
int shmid;
shmid = shmget(MY_KEY, 1024, IPC_CREAT);
char * c_addr;
c_addr = shmat(shmid, NULL, 0);
printf("Read from shared memory: %s",c_addr);
shmdt(c_addr);
return 0;
}
write端
#define MY_KEY 9527
char msg[] = "Hello world";
int main()
{
int shmid;
shmid = shmget(MY_KEY, 1024, IPC_CREAT);
char * p_addr;
p_addr = (shmat(shmid, NULL, 0));
memset(p_addr, '\0', sizeof(msg));
memcpy(p_addr, msg, sizeof(msg));
shmdt(p_addr);
return 0;
}
- 8、消息队列
将消息放入一个容器中,实际上是一个结构体。
const int MY_TYPE = 9527;
char msg[] = "Hello world";
int main()
{
pid_t pid;
pid = fork();
int msgid;
struct msgbuf
{
long mtype;
char mtext[100];
int number;
};
struct msgbuf buff;
msgid = msgget(IPC_PRIVATE, IPC_CREAT);
if(pid > 0)
{
sleep(0);
buff.mtype = MY_TYPE;
std::cout << "Please enter a string you want to send: " << std::endl;
std::cin >> buff.mtext;
std::cout << "Please enter a number you want to send: " << std::endl;
std::cin >> buff.number;
msgsnd(msgid, &buff, sizeof(buff) - sizeof(buff.mtype), 0);
waitpid(pid, NULL, 0);
}
else if(pid == 0)
{
sleep(10);
std::cout << "Child process is waiting for msg: " << std::endl;
msgrcv(msgid, &buff, sizeof(buff) - sizeof(buff.mtype), MY_TYPE, 0); //由于创建的父子进程均有buff,且不是同一个,故可以这样操作
std::cout << "Child process read msg: " << buff.mtext << "\t"<< buff.number << std::endl;
msgctl(msgid, IPC_RMID,nullptr);
}
else
{
std::cout << "fork()" << std::endl;
}
return 0;
}
非亲缘关系的进程之间使用消息队列通信,需要使用相同的消息类型以及键值。
wirte端:
#define MY_TYPE 9527
#define MY_KEY 1314
int main()
{
int msgid;
struct msgbuf
{
long mtype;
char mtext[100];
int number;
};
struct msgbuf buff;
msgid = msgget(MY_KEY,IPC_CREAT);
buff.mtype = MY_TYPE;
printf("Please enter a string you want to send: \n");
gets(buff.mtext);
printf("Please enter a number you want to send: \n");
scanf("%d", &buff.number);
msgsnd(msgid, &buff, sizeof(buff) - sizeof(buff.mtype), 0);
return 0;
}
read端:
#define MY_TYPE 9527
#define MY_KEY 1314
int main()
{
int msgid;
struct msgbuf
{
long mtype;
char mtext[100];
int number;
};
struct msgbuf buff;
msgid = msgget(MY_KEY,IPC_CREAT);
buff.mtype = MY_TYPE;
while (1)
{
printf("Process is waiting for msg: \n");
msgrcv(msgid, &buff, sizeof(buff) - sizeof(buff.mtype), MY_TYPE, 0);
printf("Process read from msg: %s, %d \n", buff.mtext, buff.number);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}