关于进程与线程,之前已经分享过基本定义以及进程线程的同步和通信。本文在此基础上,介绍一些并发应用的选择优劣以及相应的实现源码
前言:为什么要并发
很多时候程序可能需要多种功能,如果只有一个主函数,就像一个快餐点的服务员,既要在前台接待客户点餐,又要接电话送外卖,没有分身术肯定会忙得你焦头烂额的。幸运的是确实有这么一种技术,让你可以分身,可以一条线在main函数里跟你的客户交流,另一条线,你早就把你外卖送到了其他客户的手里,乐哉乐哉地轻松应付一切状况,这就是多进程/线程技术。
1. 选择的根据
多进程与多线程总结https://blog.csdn.net/lishenglong666/article/details/8557215
选择的重要基础:线程快而进程可靠性高线程有个别名叫“轻量级进程”,在有的书籍资料上介绍线程可以十倍、百倍的效率快于进程; 而进程之间不共享数据,没有锁问题,结构简单,一个进程崩溃不像线程那样影响全局,因此比较可靠。
2.简单应用例子
2.1多进程
最简单的例子,下面的代码在执行的时候:进入main函数,这就是一个进程,进程pid会打印出来,然后运行到return,该函数就退出,然后由于该函数是该进程的唯一的一次执行,所以return后,该进程也会退出
int main()
{
printf(”pid is %d/n”,getpid() );
return 0;
}
linux下fork多进程
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
void print_exit()
{
printf("the exit pid:%d/n",getpid() );
}
main ()
{
pid_t pid;
atexit( print_exit ); //注册该进程退出时的回调函数
pid=fork();
if (pid < 0)
printf("error in fork!");
else if (pid == 0)
printf("i am the child process, my process id is %d/n",getpid());
else
{
printf("i am the parent process, my process id is %d/n",getpid());
sleep(2);
wait();
}
}
程序输出如下:
i am the child process, my process id is 15806
the exit pid:15806
i am the parent process, my process id is 15805
the exit pid:15805
关于fork函数,功能就是产生子进程,由于前面说过,进程就是执行的流程活动。
那么fork产生子进程的表现就是它会返回2次:一次返回0,顺序执行下面的代码。这是子进程。一次返回子进程的pid,也顺序执行下面的代码,这是父进程。(为何父进程需要获取子进程的pid呢?这个有很多原因,其中一个原因:看最后的wait,就知道父进程等待子进程的终结后,处理其task_struct结构,否则会产生僵尸进程)
额外说下atexit( print_exit ); 需要的参数肯定是函数的调用地址。所以此处print_exit 是函数指针。
2.2 多线程
线程是可执行代码的可分派单元。简而言之,线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。
先看linux提供的多线程的系统调用:
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void),
void *restrict arg);
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
多线程例子如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* task1(void*);
void* task2(void*);
void usr();
int p1,p2;
int main()
{
usr();
getchar();
return 1;
}
void usr()
{
pthread_t pid1, pid2;
pthread_attr_t attr;
void *p;
int ret=0;
pthread_attr_init(&attr); //初始化线程属性结构
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置attr结构为分离
pthread_create(&pid1, &attr, task1, NULL); //创建线程,返回线程号给pid1,线程属性设置为attr的属性,线程函数入口为task1,参数为NULL
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&pid2, &attr, task2, NULL);
//前台工作
ret=pthread_join(pid2, &p); //等待pid2返回,返回值赋给p
printf("after pthread2:ret=%d,p=%d/n", ret,(int)p);
}
void* task1(void *arg1)
{
printf("task1/n");
//艰苦而无法预料的工作,设置为分离线程,任其自生自灭
pthread_exit( (void *)1);
}
void* task2(void *arg2)
{
int i=0;
printf("thread2 begin./n");
//继续送外卖的工作
pthread_exit((void *)2);
}
这个多线程的例子应该很明了了,主线程做自己的事情,生成2个子线程,task1为分离,任其自生自灭,而task2还是继续送外卖,需要等待返回。(因该还记得前面说过僵尸进程吧,线程也是需要等待的。如果不想等待,就设置线程为分离线程,不等带其返回。)
3. c++11新特性atomic
C++1x除了提供传统的锁、条件变量等同步机制之外,还引入了新的atomic类型。相对于传统的mutex锁来说,atomic类型更底层,具备更好的性能,因此能用于实现诸如Lock Free等高性能并行算法。
http://www.parallellabs.com/2011/08/27/c-plus-plus-memory-model/
4.面试题
多进程与多线程的区别(各自的特点)
- 多线程:
高效的内存共享,数据共享;较轻的上下文切换开销;创建销毁切换比较简单。 - 多进程:
更强的容错性,不会一阻全阻,一个进程崩溃不会整个系统崩溃。更好的多核伸缩性,进程的使用将许多内核资源(如地址空间,页表,打开的文件)隔离,在多核系统上的可伸缩性强于多线程程序 - 多进程和多线程同样可以提高多核利用率。
其实对于创建和销毁,上下文切换,其实在Linux系统下差别不大,Window下有较大差别。
综上,多进程和多线程的最主要的区别就在资源共享,隔离问题。如果工作使用的内存较大,使用多线程可以避免CPU cache的换入换出,影响性能。
线程私有
- ID,每个线程都有自己的ID作为进程中唯一的表述。
- 一组寄存器值,用来保存状态
- 各自的堆栈
- 错误返回码,防止错误还未被处理就被其他线程修改。
- 信号屏蔽码,每个线程感兴趣的信号不同。
- 优先级
线程共享
共享的:进程的代码段,公有数据,进程打开的文件描述符,全局内存,进程的栈,堆内存等。
网友评论