C/C++6C++基础:进程,信号,/多线程,线程同步
Posted 码农编程录
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++6C++基础:进程,信号,/多线程,线程同步相关的知识,希望对你有一定的参考价值。
文章目录
1.进程:fork(),grep 空
一个进程(正在内存中运行的程序)在内存里有三部分数据:代码段(相同),堆栈段+数据段(不同)
。ps -ef (同-aux) | more。getpid库函数功能是获取进程编号,该函数没有参数,返回值是进程的编号,int a;a=getpid();相同程序在不同时间执行,进程编号不同。
进程应用:并发:把socket服务端改为多进程,每次accept到一个客户端连接后,生成一个子进程,让子进程负责与这个客户端通讯。父进程继续accept客户端连接
。以下在服务端中,在CTcpServer类中增加两个成员函数。
僵尸进程
:一个进程执行了exit系统调用退出时会向父进程发送SIGCHLD信号,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态,如进程ID、终止状态等等)的进程,ps显示有< default >。总结:仔细处理子进程死后的信息,如果不想处理就需要交给系统处理。
2.信号:signal(.,EXIT),jps
进程间通信方式:1.管道
:ls | grep 1。
2.消息队列
:内核创建的一个消息队列,os中多个进程都能操作这个消息队列,可以往里面发送消息,可以接收消息。类似socket。
3.共享内存
:每个进程访问内存时,有一个虚拟内存地址和物理内存地址的一个映射,一般两个进程的虚拟内存地址可以一样,但映射的物理内存地址
一般不一样。共享内存
就是将它们映射的物理内存地址也变一样,这时两个进程能同时访问一块相同的物理内存,于是借助这块物理内存实现通信。
4.套接字
:常见,访问数据库进程和数据库进程本身,这两个进程间通信就是通过3306号端口建立起的tcp套接字。本机访问mysql不走tcp的套接字,而是走linux底层套接字。
5.信号量/灯
:类似一个计数器,控制多个进程对一个共享资源的访问,起到控制数量的锁机制。
6.信号
:一个进程可向另一个进程发送一个信号,进程可处理这个信号。如下列出所有信号,linux中信号大多数是把另一个进程杀死,干脆把这个指令叫kill了,如下64种死法。 如下是键盘中断ctrl+c,是当前shell向tail -f这个进程发送一个信号值为2的2)SIGINT
的信号。
如下kill后产生如下Terminated,kill不加参数是信号量是15的15)SIGTERM
的信号,kill和killall一样。
如下重新查看进程号,信号量是9的9)SIGKILL
的信号。
2.1 捕捉信号:ctrl+c:2
如下在死循环之前注册下信号的处理,如下让ctrl+c无效。
如下点击Build后就会将类编译出来。
如上ctrl+c无法停止,只能用kill,jps查看进程pid。
2.2 捕捉信号:kill -9:9
如下启动程序就报错。
2.3 捕捉信号:kill:15
将如上KILL换成TERM即SIGTERM,重新build project。win下信号比linux下信号少很多,将当前程序打包成jar包传到linux。可使用IDEA提供的打包jar包方式,也可用如下命令行打包。
如下用压缩软件打开1.jar。
如下:后有一个空格,最后一行是空行。
如上完整,如下可以正常运行了,再将1.jar拖进linux下。
如下kill -9才结束进程。
2.4 程序后台运行两种方法:ctrl+c无法中止,用killall book1或kill 进程号
信号作用:服务程序在后台运行,如果想终止它,杀了它不是个好办法,因为没有释放资源。如果能向程序发一个信号,程序收到这个信号后调用一个函数,在函数中编写释放资源代码,程序就可以安全体面退出。
下面 EXIT函数就是自定义函数,TcpServer设为全局变量因为EXIT函数要访问它并关闭socket。
如下ctrl+c和kill/killall命令都能调用EXIT函数来关闭进程,不叫杀进程了,叫通知退出。
3.多线程:pthread_create(),查看线程top -H,ps -xH | grep
新进程必须分配独立地址空间,每个进程有自己的堆栈段和数据段,系统开销高,数据传递
只能通过进程间通信方式。
thread
:指向线程标识符地址。attr
:设置线程属性,一般为空表示默认属性。start_routine
:线程运行函数的地址。arg
:线程运行函数的参数。编译时加上-lpthread参数,以调用静态链接库,因为pthread并非linux系统默认库。
新客户端连上启动一个线程而不是进程,下面为查看线程的主函数怎么写,pthread_create是C语言库函数,yum install -y man-pages。
如下在如上一行输出的页面EXAMPLE中找到。
将thread_start改为pth_main(线程主函数),以下在book242服务端中。
下面打印出socket。
3.1 子线程未执行:join
如下线程thread和进程process区别。
如下线程主函数void* 。
如上没有打印出hello word,如下join等待进行改进。
3.2 线程传参区分线程:“th1”
如下改进上面,往线程里传参区分线程。
3.3 两子线程数字相加:分别加到自己线程变量中
写两条线程将5000个数字加起来。
如下解决上面代码重复太多问题,将0-2500和2500-5000当参数传进来。result当成myfunc参数传进去,又可以从myfunc传出来。rand()是伪随机数,每次结果都一样。
3.4 两个线程同时加到一个全局变量s中:5000数字小不会影响
不用每个线程的result。
3.5 全局变量S++要加锁:数字大出现race condition
如下每一条线程加1000000,两条线程应该为2000000。解决就是加锁。
如下t是时间,th2 写w(s)=1,s经过2个线程,应该为2。
解决:pthread_mutex_t 结构体,如下一共做了10万次加锁和解锁(时间太长),一段代码前后都要加解锁才能原子性。
如下从时间上看两个for循环各自独立存在(相当于单线程),不如写两个for循环在同一个线程里,这样还省去了4次加锁解锁时间。
这就是为什么在3.3中把大数组拆成两段,两个线程分别加自己内容,最后放入main中加起来,这样才不会race condition,也不需要通过锁解决race condition。
3.6 假共享:和3.3一样两线程分别加到自己result数组中
0和1两个线程,两个result数组。
如下定义s为局部变量 = 结构体取出result,比上面要快。
如上完整,如下example6始终比example5快,将50000000多加一个0,快的更多。
为什么example6会比5快 ? 因为假共享false sharing。如下是单核cpu不会false sharing。
如下多核+运算结果距离近
:example5里result变量在线程主函数外,cpu线程计算要从RAM中拉取。example6里的s为局部变量放在两个线程主函数里即cpu缓存里做计算,cpu两个核里两个缓存不会互相影响。所以example6不会falsing sharing,速度快。
如下解决假共享:cache短,RAM里很长,第一个线程结果保存在0位置,第二个线程结果保存在100位置,cache只更新自己长度的一小段如下4段(空间换时间)。
如下id即0和1,是两个线程的id。线程0存0位置,线程1存100位置。
4.socket客户端程序改为多线程:pthread_mutex_destroy
#include "_public.h"
#include <pthread.h>
//xx pthread_mutex_t mutex; // 申明一个互斥锁
// 与客户端通信线程的主函数
void *pth_main(void *arg)
int pno=(long)arg; // 线程编号
pthread_detach(pthread_self());
char strbuffer[1024];
for (int ii=0;ii<3;ii++) // 与服务端进行3次交互。
//xx pthread_mutex_lock(&mutex); // 加锁
memset(strbuffer,0,sizeof(strbuffer));
sprintf(strbuffer,"线程%d:这是第%d个超级女生,编号%03d。",pno,ii+1,ii+1);
if (TcpClient.Send(strbuffer,strlen(strbuffer))<=0) break;
printf("发送:%s\\n",strbuffer);
memset(strbuffer,0,sizeof(strbuffer));
if (TcpClient.Recv(strbuffer,sizeof(strbuffer))<=0) break;
printf("线程%d接收:%s\\n",pno,strbuffer);
//xx pthread_mutex_unlock(&mutex); // 释放锁
// usleep(100); // usleep(100),否则其它的线程无法获得锁。
pthread_exit(0);
int main()
// 向服务器发起连接请求
if (TcpClient.ConnectToServer("172.16.0.15",5051)==false)
printf("TcpClient.ConnectToServer(\\"172.16.0.15\\",5051) failed,exit...\\n"); return -1;
//xx pthread_mutex_init(&mutex,0); // 创建锁
pthread_t pthid1,pthid2;
pthread_create(&pthid1,NULL,pth_main,(void*)1); // 创建第一个线程
pthread_create(&pthid2,NULL,pth_main,(void*)2); // 创建第二个线程
pthread_join(pthid1,NULL); // 等待线程1退出。
pthread_join(pthid2,NULL); // 等待线程2退出。
//xx pthread_mutex_destroy(&mutex[ii]); // 销毁锁
客户端成功连上服务器后,创建两个线程,同时与服务端进行通信,发送3个请求报文并接收服务端的回应。book263.cpp暂时不启用锁,先试试效果。启动服务端程序book261,然后再启动book263。
发现客户端的两个线程的报文收发出现了混乱。把book263.cpp的线程锁代码启用,编译运行。
以上是关于C/C++6C++基础:进程,信号,/多线程,线程同步的主要内容,如果未能解决你的问题,请参考以下文章
Python笔记Python多线程进程如何正确响应Ctrl-C以实现优雅退出