最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

調(diào)用Process.waitfor導(dǎo)致的進(jìn)程掛起問(wèn)題及解決

 更新時(shí)間:2021年12月14日 09:04:38   作者:我是安靜的美男子  
這篇文章主要介紹了調(diào)用Process.waitfor導(dǎo)致的進(jìn)程掛起問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

問(wèn)題背景

如果要在Java中調(diào)用shell腳本時(shí),可以使用Runtime.exec或ProcessBuilder.start。它們都會(huì)返回一個(gè)Process對(duì)象,通過(guò)這個(gè)Process可以對(duì)獲取腳本執(zhí)行的輸出,然后在Java中進(jìn)行相應(yīng)處理。

例如,下面的代碼:

		try 
		{
			Process process = Runtime.getRuntime().exec(cmd);			
			process.waitFor();                        
                        //do something ...
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

通常,安全編碼規(guī)范中都會(huì)指出:使用Process.waitfor的時(shí)候,可能導(dǎo)致進(jìn)程阻塞,甚至死鎖。 那么這句應(yīng)該怎么理解呢?用個(gè)實(shí)際的例子說(shuō)明下。

問(wèn)題描述

使用Java代碼調(diào)用shell腳本,執(zhí)行后會(huì)發(fā)現(xiàn)Java進(jìn)程和Shell進(jìn)程都會(huì)掛起,無(wú)法結(jié)束。

Java代碼 processtest.java

		try 
		{
			Process process = Runtime.getRuntime().exec(cmd);
			System.out.println("start run cmd=" + cmd);
			
			process.waitFor();
			System.out.println("finish run cmd=" + cmd);
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

被調(diào)用的Shell腳本doecho.sh

#!/bin/bash
for((i=0; ;i++))
do    
    echo -n "0123456789"
    echo $i >> count.log
done

掛起原因

  • 主進(jìn)程中調(diào)用Runtime.exec會(huì)創(chuàng)建一個(gè)子進(jìn)程,用于執(zhí)行shell腳本。子進(jìn)程創(chuàng)建后會(huì)和主進(jìn)程分別獨(dú)立運(yùn)行。
  • 因?yàn)橹鬟M(jìn)程需要等待腳本執(zhí)行完成,然后對(duì)腳本返回值或輸出進(jìn)行處理,所以這里主進(jìn)程調(diào)用Process.waitfor等待子進(jìn)程完成。
  • 通過(guò)shell腳本可以看出:子進(jìn)程執(zhí)行過(guò)程就是不斷的打印信息。主進(jìn)程中可以通過(guò)Process.getInputStream和Process.getErrorStream獲取并處理。
  • 這時(shí)候子進(jìn)程不斷向主進(jìn)程發(fā)生數(shù)據(jù),而主進(jìn)程調(diào)用Process.waitfor后已掛起。當(dāng)前子進(jìn)程和主進(jìn)程之間的緩沖區(qū)塞滿后,子進(jìn)程不能繼續(xù)寫數(shù)據(jù),然后也會(huì)掛起。
  • 這樣子進(jìn)程等待主進(jìn)程讀取數(shù)據(jù),主進(jìn)程等待子進(jìn)程結(jié)束,兩個(gè)進(jìn)程相互等待,最終導(dǎo)致死鎖。

解決方法

基于上述分析,只要主進(jìn)程在waitfor之前,能不斷處理緩沖區(qū)中的數(shù)據(jù)就可以。因?yàn)?,我們可以再waitfor之前,單獨(dú)啟兩個(gè)額外的線程,分別用于處理InputStream和ErrorStream就可以。實(shí)例代碼如下:

		try 
		{
			final Process process = Runtime.getRuntime().exec(cmd);
			System.out.println("start run cmd=" + cmd);
			
			//處理InputStream的線程
			new Thread()
			{
				@Override
				public void run()
				{
					BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); 
					String line = null;
					
					try 
					{
						while((line = in.readLine()) != null)
						{
							System.out.println("output: " + line);
						}
					} 
					catch (IOException e) 
					{						
						e.printStackTrace();
					}
					finally
					{
						try 
						{
							in.close();
						} 
						catch (IOException e) 
						{
							e.printStackTrace();
						}
					}
				}
			}.start();
			
			new Thread()
			{
				@Override
				public void run()
				{
					BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream())); 
					String line = null;
					
					try 
					{
						while((line = err.readLine()) != null)
						{
							System.out.println("err: " + line);
						}
					} 
					catch (IOException e) 
					{						
						e.printStackTrace();
					}
					finally
					{
						try 
						{
							err.close();
						} 
						catch (IOException e) 
						{
							e.printStackTrace();
						}
					}
				}
			}.start();
			
			process.waitFor();
			System.out.println("finish run cmd=" + cmd);
		} 
		catch (Exception e) 
		{			
			e.printStackTrace();
		}

JDK上的說(shuō)明

By default, the created subprocess does not have its own terminal or console.

All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream().

The parent process uses these streams to feed input to and get output from the subprocess.

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.

從JDK的說(shuō)明中可以看出兩點(diǎn):

  • 如果系統(tǒng)中標(biāo)準(zhǔn)輸入輸出流使用的bufffer大小有限,所有讀寫時(shí)可能會(huì)出現(xiàn)阻塞或死鎖。------這點(diǎn)上面已分析
  • 子進(jìn)程的標(biāo)準(zhǔn)I/O已經(jīng)被重定向到了父進(jìn)程。父進(jìn)程可以通過(guò)對(duì)應(yīng)的接口獲取到子進(jìn)程的I/O。------I/O是如何重定向的?

背后的故事

要回答上面的問(wèn)題可以從系統(tǒng)的層面嘗試分析。

首先通過(guò)ps命令可以看到,在linux上多出了兩個(gè)進(jìn)程:一個(gè)Java進(jìn)程、一個(gè)shell進(jìn)程,且shell是java的子進(jìn)程。

然后,可以看到shell進(jìn)程的狀態(tài)顯示為pipe_w。我剛開始以為pipe_w表示pipe_write。進(jìn)一步查看/proc/pid/wchan發(fā)現(xiàn)pipe_w其實(shí)表示為pipe_wait。通常/proc/pid/wchan表示一個(gè)內(nèi)存地址或進(jìn)程正在執(zhí)行的方法名稱。因此,這似乎表明該進(jìn)程在操作pipe時(shí)發(fā)生了等待,從而被掛起。我們知道pipe是IPC的一種,通常用于父子進(jìn)程之間通信。這樣我們可以猜測(cè):可能是父子進(jìn)程之間通過(guò)pipe通信的時(shí)候出現(xiàn)了阻塞。

另外,觀察父子進(jìn)程的fd信息,即/proc/pid/fd??梢钥吹阶舆M(jìn)程的0/1/2(即:stdin/stdout/stderr)分別被重定向到了三個(gè)pipe文件;父親進(jìn)程中對(duì)應(yīng)的也有對(duì)著三個(gè)pipe文件的引用。

綜上所述,這個(gè)過(guò)程應(yīng)該是這樣的:子進(jìn)程不斷向pipe中寫數(shù)據(jù),而父進(jìn)程一直不讀取pipe中的數(shù)據(jù),導(dǎo)致pipe被塞滿,子進(jìn)程無(wú)法繼續(xù)寫入,所以出現(xiàn)pipe_wait的狀態(tài)。那么pipe到底有多大呢?

測(cè)試pipe的大小

因?yàn)槲乙呀?jīng)在doecho.sh的腳步中記錄了打印了字符數(shù),查看count.log就可以知道子進(jìn)程最終發(fā)送了多少數(shù)據(jù)。在子進(jìn)程掛起了,count.log的數(shù)據(jù)一致保持在6543不變。故,當(dāng)前子進(jìn)程向pipe中寫入6543*10=65430bytes時(shí),出現(xiàn)進(jìn)程掛起。65536-65430=106byte即距離64K差了106bytes。

換另外的測(cè)試方式,每次寫入1k,記錄總共可以寫入多少。進(jìn)程代碼如test_pipe_size.sh所示。測(cè)試結(jié)果為64K。兩次結(jié)果相差了106byte,那個(gè)這個(gè)pipe到底多大?

Linux上pipe分析

最直接的方式就是看源碼。Pipe的實(shí)現(xiàn)代碼主要在linux/fs/pipe.c中,我們主要看pipe_wait方法。

 pipe_read(struct kiocb *iocb, struct iov_iter *to)
 {
         size_t total_len = iov_iter_count(to);
         struct file *filp = iocb->ki_filp;
         struct pipe_inode_info *pipe = filp->private_data;
         int do_wakeup;
         ssize_t ret;
 
         /* Null read succeeds. */
         if (unlikely(total_len == 0))
                 return 0;
 
         do_wakeup = 0;
         ret = 0;
         __pipe_lock(pipe);
         for (;;) {
                 int bufs = pipe->nrbufs;
                 if (bufs) {
                         int curbuf = pipe->curbuf;
                         struct pipe_buffer *buf = pipe->bufs + curbuf;
                         const struct pipe_buf_operations *ops = buf->ops;
                         size_t chars = buf->len;
                         size_t written;
                         int error;
 
                         if (chars > total_len)
                                 chars = total_len;
 
                         error = ops->confirm(pipe, buf);
                         if (error) {
                                 if (!ret)
                                         ret = error;
                                 break;
                         }
 
                         written = copy_page_to_iter(buf->page, buf->offset, chars, to);
                         if (unlikely(written < chars)) {
                                 if (!ret)
                                         ret = -EFAULT;
                                 break;
                         }
                         ret += chars;
                         buf->offset += chars;
                         buf->len -= chars;
 
                         /* Was it a packet buffer? Clean up and exit */
                         if (buf->flags & PIPE_BUF_FLAG_PACKET) {
                                 total_len = chars;
                                 buf->len = 0;
                         }
 
                         if (!buf->len) {
                                 buf->ops = NULL;
                                 ops->release(pipe, buf);
                                 curbuf = (curbuf + 1) & (pipe->buffers - 1);
                                 pipe->curbuf = curbuf;
                                 pipe->nrbufs = --bufs;
                                 do_wakeup = 1;
                         }
                         total_len -= chars;
                         if (!total_len)
                                 break;  /* common path: read succeeded */
                 }
                 if (bufs)       /* More to do? */
                         continue;
                 if (!pipe->writers)
                         break;
                 if (!pipe->waiting_writers) {
                         /* syscall merging: Usually we must not sleep
                          * if O_NONBLOCK is set, or if we got some data.
                          * But if a writer sleeps in kernel space, then
                          * we can wait for that data without violating POSIX.
                          */
                         if (ret)
                                 break;
                         if (filp->f_flags & O_NONBLOCK) {
                                 ret = -EAGAIN;
                                 break;
                         }
                 }
                 if (signal_pending(current)) {
                         if (!ret)
                                 ret = -ERESTARTSYS;
                         break;
                 }
                 if (do_wakeup) {
                         wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
                         kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
                 }
                 pipe_wait(pipe);
         }
         __pipe_unlock(pipe);
 
         /* Signal writers asynchronously that there is more room. */
         if (do_wakeup) {
                 wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
                 kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
         }
         if (ret > 0)
                 file_accessed(filp);
         return ret;
 }

可以看到Pipe被組織成環(huán)狀結(jié)構(gòu),即一個(gè)循環(huán)鏈表。鏈表中的元素為struct pipe_buffer的結(jié)構(gòu),每個(gè)pipe_buffer對(duì)于一個(gè)page。鏈表中共有16個(gè)元素,即pipe buffer的總大小為16*page。如果page大小為4K,那么pipe buffer的總大小應(yīng)該為16*4K=64K。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • java、python、JavaScript以及jquery循環(huán)語(yǔ)句的區(qū)別

    java、python、JavaScript以及jquery循環(huán)語(yǔ)句的區(qū)別

    本篇文章主要介紹java、python、JavaScript以及jquery的循環(huán)語(yǔ)句的區(qū)別,這里整理了它們循環(huán)語(yǔ)句語(yǔ)法跟示例,以便大家閱讀,更好的區(qū)分它們的不同
    2016-07-07
  • Java中Function的使用及說(shuō)明

    Java中Function的使用及說(shuō)明

    這篇文章主要介紹了Java中Function的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Mybatis generator mapper文件覆蓋原文件的示例代碼

    Mybatis generator mapper文件覆蓋原文件的示例代碼

    這篇文章主要介紹了Mybatis generator mapper文件覆蓋原文件,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • SpringBoot使用mybatis步驟總結(jié)

    SpringBoot使用mybatis步驟總結(jié)

    今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著SpringBoot使用mybatis步驟展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究

    HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究

    這篇文章主要為大家介紹了HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • 為zookeeper配置相應(yīng)的acl權(quán)限

    為zookeeper配置相應(yīng)的acl權(quán)限

    這篇文章主要介紹了為zookeeper配置相應(yīng)的acl權(quán)限的相關(guān)實(shí)例,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-09-09
  • Springboot項(xiàng)目啟動(dòng)優(yōu)化方式

    Springboot項(xiàng)目啟動(dòng)優(yōu)化方式

    文章詳細(xì)介紹了Spring Boot項(xiàng)目的啟動(dòng)優(yōu)化策略,包括懶加載、異步初始化、精簡(jiǎn)依賴、JVM優(yōu)化和使用Actuator監(jiān)控等方法,旨在提高項(xiàng)目的啟動(dòng)速度和運(yùn)行性能
    2025-03-03
  • Java中toString()、String.valueOf、(String)強(qiáng)轉(zhuǎn)區(qū)別

    Java中toString()、String.valueOf、(String)強(qiáng)轉(zhuǎn)區(qū)別

    相信大家在日常開發(fā)中這三種方法用到的應(yīng)該很多,本文主要介紹了Java中toString()、String.valueOf、(String)強(qiáng)轉(zhuǎn)區(qū)別,感興趣的可以了解一下
    2021-09-09
  • Java中的cglib代理詳解

    Java中的cglib代理詳解

    這篇文章主要介紹了Java中的cglib代理詳解, 代理模式是一種設(shè)計(jì)模式,它可以為其他對(duì)象提供一種代理,以控制對(duì)該對(duì)象的訪問(wèn),可以在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象,而不需要手動(dòng)編寫代理類的代碼,需要的朋友可以參考下
    2023-09-09
  • 淺談Spring Data如何簡(jiǎn)化數(shù)據(jù)操作的方法

    淺談Spring Data如何簡(jiǎn)化數(shù)據(jù)操作的方法

    這篇文章主要介紹了看Spring Data如何簡(jiǎn)化數(shù)據(jù)操作的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-04-04

最新評(píng)論

安义县| 昌黎县| 南丹县| 玉山县| 阜宁县| 禹州市| 涿州市| 西宁市| 吉水县| 普格县| 大兴区| 神农架林区| 南华县| 类乌齐县| 武山县| 台江县| 黄平县| 久治县| 讷河市| 安义县| 花莲市| 临城县| 电白县| 新疆| 塔河县| 张掖市| 天津市| 普安县| 中卫市| 特克斯县| 舒兰市| 天津市| 禄丰县| 辉南县| 秦安县| 利辛县| 涞源县| 宜城市| 舒兰市| 勃利县| 德昌县|