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以实现优雅退出

Python笔记Python多线程进程如何正确响应Ctrl-C以实现优雅退出

Java基础学习——多线程

python基础(21)-线程通信

Linux多任务编程——线程

Python网络编程(进程通信信号线程锁多线程)