must read

1.解决hash 碰撞的问题? 单链表还是双链表

hash函数就是把任意长的输入字符串变化成固定长的输出字符串的一种函数。通俗得说,hash函数用来生成信息的摘要。输出字符串的长度称为hash函数的位数。hashmap 存储数据是把key hash话,生成一个hash数值,这个hash数值代表节点存储的地址,hash碰撞就是不同的Key生成同样的hash值,Hashmap里面的bucket出现了单链表的形式,散列表要解决的一个问题就是散列值的冲突问题,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。java.util.HashMap采用的链表法的方式,链表是单向链表。 单向链表与双向链表查询时间复杂度都是o(n),删除时间复杂度双向链表是O(1)(指的是删除本节点),单向链表是O(N);;;

网易公开课算法导论第七课 ——哈希表

这一课讲了哈希表,的确耗费了我很多时间去理解哈希表的相关内容。
从作用上来讲,构建哈希表的目的是把搜索的时间复杂度降低到O(1),考虑到一个长度为n的序列,如果依次去比较进行搜索的话,时间复杂度是θ(n),或者对其先进行排序然后再搜索会更快一些,但这两种方法都不是最快的方法。
哈希表也叫散列表,他通过一个哈希函数H,把要存储的内容取一个键值,经过H的运算,把键值映射到一个有m个槽的表中去,最简单的例子就是手机里存储别人的电话号码,键值就是名字,内容就是电话号码等个人信息。这样的表有一个最大的好处就是一旦要查找某个值,比如说“张三”的电话号码,我们把“张三”这个名字带入H函数,就能返回一个存储位置,比如说存在了号码本的第45个位置,我们就能立刻在这个位置搜索到“张三”的电话号码。

第一个话题:
计算机里面所有存储的内容都是数字,因此我们研究对数字构建哈希表就够了。先来考虑一下,一个好的哈希函数H需要哪些特点:
1.哈希函数的产生的键值要尽可能的均匀,不要出现聚集效应,也就是产生的各种h(k)要尽量的等概率的分布到哈希表的m个槽里去,当然,如果已经知道了输入的类型,我们可以设计出比较好的哈希函数,但是每一个哈希函数都有可能遇到一个特别针对他的输入,以至于所有计算的健值都指向同一个槽。
(一个哈希函数是否均匀的定义:x,y是两个不同的健值,哈希表的长度是m,P{h(x) = h(y)} =1/m ) 
2.哈希函数本身不能太复杂,以至于计算的时间过长。

例,一些简单的哈希函数:
1.直接哈希 h(k)=k,不会发生碰撞,但是占用空间大,没有意义
2.除法哈希 h(k)=k mod m ,m的取值很有讲究,不能去2和10的幂,这样很多内容都被mod掉了,而且不能取得太小等等等,可以考虑去一个合适的质数。由于计算机里经常用2和10的幂,不是很好用质数,而且还是除法,所以这种哈希效率也不是很高
3.乘法哈希,假设所有的key都是整数,m=2^r ,计算机字长是w,那么构建h(k)=(A*k mod 2^w) rsh (w-r) 其中rsh是右移的意思,A的大小是2^(w-1)<A<2^w 这个哈希函数的好处是,最后的取得h(k)实际上和每一位上的k值都相关,而A和2^w这两个数是互质的,所以想象一个轮盘,周长是2幂,A肯定不是周长的倍数,k是转了多少圈,那么最后的h(k)就会有可能落到轮盘的任意位置。

第二个话题:
就像之前提到的,无论设计一个怎样的哈希函数,碰撞都难以避免,那么如何来解决碰撞的问题呢?主要有以下两种方法:
1.链接法,每一次碰撞都添加一个链表,这样做会增加哈希表的大小,最坏的情况会导致所有的值都指向同一个槽,然后哈希表变成了一个链表,我们的查询也变成了链表的查询。
2.开放寻址法,在不增加哈希表容量的情况下,继续对该表进行“探测”,直到找到一个空位置把内容放进去。 wikipedia里面对此的解释是这样的(Open addressing, or closed hashing, is a method of collision resolution in hash tables. With this method a hash collision is resolved by probing

分析第一种方法——链表法:
在最坏的情况下,那就是所有的h(k)都指向了同一个槽,那么哈希表实际上就是一个链表,在链表中查询一个值的时间复杂度是θ(n),在最好的情况下,没有发生碰撞那么时间为θ(1)。定义α=n/m为哈希表的装载因子,一次成功的搜索平均用时θ(1+α/2)1表示计算H的时间,α/2表示在链表中所用的平均时间,所以如果n=O(m)那么α就是常数,在这个哈希表中搜索的时间就为θ(1),同时,考虑平均情况下的最坏情况的搜索,时间为θ(1+α),

分析第二种方法——“开放寻址”(封闭哈希)
这种方法主要通过“探寻”来在哈希表中寻找下一个空位置,把值存进去,查询的时候也采用同样的方法,一步一步查找到目标键值。
探寻的方法有:
1.线性探寻
2.非线性探寻
3.双重哈希探寻
4.伪随机序列探寻
这些方法都有一定的局限性,有可能造成顶级或者次级聚集
现在来分析开放寻址的效率,首先给出理论:对于一个开放寻址的哈希表,α=n/m<1,那么一次不成功搜索的预期探寻次数为1/(1-α).
由此可见,如果α=50% 那么预期探寻次数为2,如果α=90%,预期探寻次数将会显著升高到10,因此在这种策略下,α的大小至关重要(联想到同一天生日的问题,也是这个道理),在工程上某些采用此策略的哈希表会强制α小于75%,如果超过这个值会自动扩充哈希表。
预期探寻次数1/(1-α)是怎样算出来的,如下:
1.首先,查询一个值至少需要1次探寻
2.有n/m的可能性会发生碰撞,我们需要第二次探寻
3.有(n-1)/(m-1)的可能性第二次探寻也发生了碰撞
……
观察到(n-i)/(m-i)<α i=1,2,3……n

2.setsokcetopt()函数作用,可用选项哪些?

1. [code]每个套接口都有一个发送缓冲区和一个接收缓冲区。 接收缓冲区被TCP和UDP用来将接收到的数据一直保存到由应用进程来读。 TCP:TCP通告另一端的窗口大小。 TCP套接口接收缓冲区不可能溢出,因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过宙口大小的数据,则接 收方TCP将丢弃它。 UDP:当接收到的数据报装不进套接口接收缓冲区时,此数据报就被丢弃。UDP是没有 流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。[/code
2.[code]
我们经常听说tcp协议的三次握手,但三次握手到底是什么,其细节是什么,为什么要这么做呢?
第一次:客户端发送连接请求给服务器,服务器接收;
第二次:服务器返回给客户端一个确认码,附带一个从服务器到客户端的连接请求,客户机接收,确认客户端到服务器的连接.
第三次:客户机返回服务器上次发送请求的确认码,服务器接收,确认服务器到客户端的连接.
我们可以看到:
1. tcp的每个连接都需要确认.
2. 客户端到服务器和服务器到客户端的连接是独立的.
我们再想想tcp协议的特点:连接的,可靠的,全双工的,实际上tcp的三次握手正是为了保证这些特性的实现. [/code]
3.setsockopt的用法

1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));


2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));


3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));


4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));


5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));


6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));


7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));


8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));


9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

3.内存泄露,段错误,cpu高负载,死锁相关调试方法


4.虚析构函数作用


5.fork() wait()函数作用

#include <sys/types.h> /* 提供类型pid_t的定义 */

#include <sys/wait.h>

pid_t wait(int *status)

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

wait调用例程:

/* wait1.c */

#include <sys/types.h>

#include <sys/wait.h>

#include <unistd.h>

#include <stdlib.h>

main()

{

        pid_t pc,pr;

        pc=fork();

 

        if(pc<0) /* 如果出错 */

               printf("error ocurred!/n");

       else if(pc==0){ /* 如果是子进程 */

               printf("This is child process with pid of %d/n",getpid());

              sleep(10); /* 睡眠10秒钟 */

            }

        else{ /* 如果是父进程 */

               pr=wait(NULL); /* 在这里等待 */

              printf("I catched a child process with pid of %d/n"),pr);

        }

       exit(0);

}

编译并运行:

$ gcc wait1.c -o wait1

$ ./wait1

This is child process with pid of 1508

I catched a child process with pid of 1508

############################################################

waitpid系统调用在Linux函数库中的原型是:

#include <sys/types.h> /* 提供类型pid_t的定义 */

#include <sys/wait.h>

pid_t waitpid(pid_t pid,int *status,int options)

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:

pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

         pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

         pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

         pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

         pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

#########################################################################

孤儿进程与僵尸进程[总结]

1、前言

  之前在看《unix环境高级编程》第八章进程时候,提到孤儿进程和僵尸进程,一直对这两个概念比较模糊。今天被人问到什么是孤儿进程和僵尸进程,会带来什么问题,怎么解决,我只停留在概念上面,没有深入,倍感惭愧。晚上回来google了一下,再次参考APUE,认真总结一下,加深理解。

2、基本概念

  我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

  孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

3、问题及危害

  unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

  孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

  僵尸进程危害场景:

  例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。

3、孤儿进程和僵尸进程测试

孤儿进程测试程序如下所示:

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <errno.h>
 4 #include <unistd.h>
 5 
 6 int main()
 7 {
 8     pid_t pid;
 9     //创建一个进程
10     pid = fork();
11     //创建失败
12     if (pid < 0)
13     {
14         perror("fork error:");
15         exit(1);
16     }
17     //子进程
18     if (pid == 0)
19     {
20         printf("I am the child process.\n");
21         //输出进程ID和父进程ID
22         printf("pid: %d\tppid:%d\n",getpid(),getppid());
23         printf("I will sleep five seconds.\n");
24         //睡眠5s,保证父进程先退出
25         sleep(5);
26         printf("pid: %d\tppid:%d\n",getpid(),getppid());
27         printf("child process is exited.\n");
28     }
29     //父进程
30     else
31     {
32         printf("I am father process.\n");
33         //父进程睡眠1s,保证子进程输出进程id
34         sleep(1);
35         printf("father process is  exited.\n");
36     }
37     return 0;
38 }
复制代码

测试结果如下:

僵尸进程测试程序如下所示:

复制代码
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <errno.h>
 4 #include <stdlib.h>
 5 
 6 int main()
 7 {
 8     pid_t pid;
 9     pid = fork();
10     if (pid < 0)
11     {
12         perror("fork error:");
13         exit(1);
14     }
15     else if (pid == 0)
16     {
17         printf("I am child process.I am exiting.\n");
18         exit(0);
19     }
20     printf("I am father process.I will sleep two seconds\n");
21     //等待子进程先退出
22     sleep(2);
23     //输出进程信息
24     system("ps -o pid,ppid,state,tty,command");
25     printf("father process is exiting.\n");
26     return 0;
27 }
复制代码

测试结果如下所示:

僵尸进程测试2:父进程循环创建子进程,子进程退出,造成多个僵尸进程,程序如下所示:

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 
 6 int main()
 7 {
 8     pid_t  pid;
 9     //循环创建子进程
10     while(1)
11     {
12         pid = fork();
13         if (pid < 0)
14         {
15             perror("fork error:");
16             exit(1);
17         }
18         else if (pid == 0)
19         {
20             printf("I am a child process.\nI am exiting.\n");
21             //子进程退出,成为僵尸进程
22             exit(0);
23         }
24         else
25         {
26             //父进程休眠20s继续创建子进程
27             sleep(20);
28             continue;
29         }
30     }
31     return 0;
32 }
复制代码

程序测试结果如下所示:

4、僵尸进程解决办法

(1)通过信号机制

  子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。测试程序如下所示:

复制代码
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <errno.h>
 4 #include <stdlib.h>
 5 #include <signal.h>
 6 
 7 static void sig_child(int signo);
 8 
 9 int main()
10 {
11     pid_t pid;
12     //创建捕捉子进程退出信号
13     signal(SIGCHLD,sig_child);
14     pid = fork();
15     if (pid < 0)
16     {
17         perror("fork error:");
18         exit(1);
19     }
20     else if (pid == 0)
21     {
22         printf("I am child process,pid id %d.I am exiting.\n",getpid());
23         exit(0);
24     }
25     printf("I am father process.I will sleep two seconds\n");
26     //等待子进程先退出
27     sleep(2);
28     //输出进程信息
29     system("ps -o pid,ppid,state,tty,command");
30     printf("father process is exiting.\n");
31     return 0;
32 }
33 
34 static void sig_child(int signo)
35 {
36      pid_t        pid;
37      int        stat;
38      //处理僵尸进程
39      while ((pid = waitpid(-1, &stat, WNOHANG)) >0)
40             printf("child %d terminated.\n", pid);
41 }
复制代码

测试结果如下所示:

(2)fork两次
  《Unix 环境高级编程》8.6节说的非常详细。原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。测试程序如下所示:

复制代码
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 
 6 int main()
 7 {
 8     pid_t  pid;
 9     //创建第一个子进程
10     pid = fork();
11     if (pid < 0)
12     {
13         perror("fork error:");
14         exit(1);
15     }
16     //第一个子进程
17     else if (pid == 0)
18     {
19         //子进程再创建子进程
20         printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
21         pid = fork();
22         if (pid < 0)
23         {
24             perror("fork error:");
25             exit(1);
26         }
27         //第一个子进程退出
28         else if (pid >0)
29         {
30             printf("first procee is exited.\n");
31             exit(0);
32         }
33         //第二个子进程
34         //睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里
35         sleep(3);
36         printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
37         exit(0);
38     }
39     //父进程处理第一个子进程退出
40     if (waitpid(pid, NULL, 0) != pid)
41     {
42         perror("waitepid error:");
43         exit(1);
44     }
45     exit(0);
46     return 0;
47 }
复制代码

测试结果如下图所示:

5、参考资料

《unix环境高级编程》第八章


6.new 和malloc 用法,区别


7.strcpy memcpy memove() 函数 使用注意点


8.TCP/IP 通信 握手及断开过程,滑动窗口,time_wait,close_wait 状态个过程


9.select 用法  epoll用法


10.进程里都有哪些存储区,分别放什么


1、栈区(stack— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。 程序结束后由系统释放。
4文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区存放函数体的二进制代码。


11.排序算法有哪些,时间复杂度是多少,是怎么计算出来的


12.vector list map 迭代器失效问题


13.map实现的数据结构是什么?  hashtable呢


14.进程间 通信 有哪些方式?都有什么特点及引用场景


15.进程间互斥方式有哪些?线程间互斥方式有哪些?

Linux下的多进程间共享资源的互斥访问

#include    <stdio.h>  
#include    <stdlib.h>  
#include    <unistd.h>  
#include    <fcntl.h>  
#include    <sys/mman.h>  
#include    <pthread.h>  
pthread_mutex_t* g_mutex;  
//创建共享的mutex  
void init_mutex(void)  
{  
    int ret;  
    //g_mutex一定要是进程间可以共享的,否则无法达到进程间互斥  
    g_mutex=(pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);  
    if( MAP_FAILED==g_mutex )  
    {  
        perror("mmap");  
        exit(1);  
    }  
      
    //设置attr的属性  
    pthread_mutexattr_t attr;  
    pthread_mutexattr_init(&attr);  
    //一定要设置为PTHREAD_PROCESS_SHARED  
    //具体可以参考http://blog.chinaunix.net/u/22935/showart_340408.html  
    ret=pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);  
    if( ret!=0 )  
    {  
        perror("init_mutex pthread_mutexattr_setpshared");  
        exit(1);  
    }  
    pthread_mutex_init(g_mutex, &attr);  
}  
int main(int argc, char *argv[])  
{  
    init_mutex();  
    int ret;      
    char str1[]="this is child process/r/n";  
    char str2[]="this is father process/r/n";  
    int fd=open("tmp", O_RDWR|O_CREAT|O_TRUNC, 0666);  
    if( -1==fd )  
    {  
        perror("open");  
        exit(1);  
    }  
    pid_t pid;  
    pid=fork();  
    if( pid<0 )  
    {  
        perror("fork");  
        exit(1);  
    }  
    else if( 0==pid )  
    {  
        ret=pthread_mutex_lock(g_mutex);  
        if( ret!=0 )  
        {  
            perror("child pthread_mutex_lock");  
        }  
        sleep(10);//测试是否能够阻止父进程的写入  
        write(fd, str1, sizeof(str1));  
        ret=pthread_mutex_unlock(g_mutex);    
        if( ret!=0 )  
        {  
            perror("child pthread_mutex_unlock");  
        }     
    }  
    else  
    {  
        sleep(2);//保证子进程先执行   
        ret=pthread_mutex_lock(g_mutex);  
        if( ret!=0 )  
        {  
            perror("father pthread_mutex_lock");  
        }  
        write(fd, str2, sizeof(str2));  
        ret=pthread_mutex_unlock(g_mutex);    
        if( ret!=0 )  
        {  
            perror("father pthread_mutex_unlock");  
        }                 
    }  
    wait(NULL);  
    munmap(g_mutex, sizeof(pthread_mutex_t));  
}  
线程间不用mmap

16.http 通信协议过程?返回码1XX 、2XX、3XX、 4XX、 5XX分别代表什么含义


17.逆序链表、链表是否有环、两个链表是否交叉、二叉树逆序


18.h264码流结构,AAC结构,音视频同步


19.rtmp/rtp/rtsp/rtcp  hls 流媒体传输协议


20.MySQL 查询 order by、group by、distinict、count、sum字段作用,sql注入原理


21.string类默认自动生成的成员函数有哪些


22.智能指针作用实现原理


23.C++ 成员函数后面加const 作用


24.长连接和短连接含义?什么场景下用长连接与短连接?各自优缺点?

(

短连接:例如普通的web请求,在三次握手之后建立连接,发送数据包并得到服务器返回的结果之后,通过客户端和服务端的四次握手进行关闭断开。

长连接:区别于短连接,由于三次握手链接及四次握手断开,在请求频繁的情况下,链接请求和断开请求的开销较大,影响效率。采用长连接方式,执行三次握手链接后,不断开链接,保持客户端和服务端通信,直到服务器超时自动断开链接,或者客户端主动断开链接。

适用场景

短连接:适用于网页浏览等数据刷新频度较低的场景。

长连接:适用于客户端和服务端通信频繁的场景,例如聊天室,实时游戏等。

)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Sure, here's an example code that implements the functionality you described: ``` import javax.swing.*; import java.awt.*; import java.awt.event.*; public class LibraryGUI extends JFrame { private JTextField userField; private JButton getBookButton; private Controller controller; public LibraryGUI() { super("Library GUI"); controller = new Controller(); // assuming you have a Controller class // create components userField = new JTextField(20); getBookButton = new JButton("Get Book"); // add action listener to button getBookButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String user = userField.getText(); String book = controller.getBook(user); JOptionPane.showMessageDialog(null, book); } }); // add components to content pane JPanel contentPane = (JPanel) getContentPane(); contentPane.setLayout(new FlowLayout()); contentPane.add(new JLabel("Enter library user:")); contentPane.add(userField); contentPane.add(getBookButton); // set window properties setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setLocationRelativeTo(null); setVisible(true); } public static void main(String[] args) { new LibraryGUI(); } } ``` In this example, when the user clicks on the "Get Book" button, the action listener reads the name of the library user typed in the text field, calls the `getBook` method of the controller with that user name as argument, and displays the resulting string in a message dialog. Note that the `JOptionPane.showMessageDialog` method is used to display the message dialog.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值