멀티쓰레드

codakcodak·2023년 12월 3일
0

wannago

목록 보기
5/5
post-thumbnail

문제점

  • 동영상에서 초당 1프레임 단위로 잘라 썸네일 이미지를 만들어야 하므로 단순 구현으로는 시간이 오래걸림
  • 모든 초에서의 작업이 끝날때까지 기다려야하므로 부분적인 동기화 작업이 필요

해결방안

  • Runnable인터페이스를 구현한 구현체를 통해 멀티쓰레드 작업으로의 전환
  • 시작한 쓰레드 작업들을 모두 배열에 추가하고 모든 쓰레드에 join을 통해 동기화 적용

구현

1. Runnable구현체

//동영상에서 특정 시각의 이미지를 잘라내어 특정 크기로 경로에 저장하는 쓰레드 작업
@Slf4j
public class ThreadStreamImage implements Runnable{
	private File savePath;
	private File source;
	private int time;
	private int width;
	private int height;
	private String format;
	
	public ThreadStreamImage(int time,File savePath,File source,int width,int height,String format) {
		this.time=time;
		this.savePath=savePath;
		this.source=source;
		this.width=width;
		this.height=height;
		this.format=format;
	}
	@Override
	public void run() {
		log.debug("=============time: "+time+" processing Streaming thumbnail start");
		FrameGrab grab;
		try {
        	//동영상(source)를 읽기
			grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(this.source));
            //특정 시각(time)으로 이동
			grab.seekToSecondPrecise(this.time);
            //이동된 시각의 프레임 이미지 잘라내기
			Picture thumbnail=grab.getNativeFrame();
            //버퍼에 잘라낸 이미지 저장
        	BufferedImage bufferedImage = AWTUtil.toBufferedImage(thumbnail);
            //특정 경로(savePath)에 가로(width),세로(height)형식으로 이미지 저장
        	Thumbnails.of(bufferedImage)
            .size(this.width,this.height)
            .outputFormat(this.format)
            .toFile(savePath);
		} catch (IOException | JCodecException e) {
			e.printStackTrace();
		}
		log.debug("time: "+time+" processing Streaming thumbnail end===============");
	}
}

2.thread작업 수행

public class FileUtilImpl implements FileUtil{
	private void generateVideoStreamingThumbnail(File source,MediaDto media) throws Exception{
    	//동영상 소스 읽기
		FrameGrab grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(source));
        // 동영상의 재생시간 추출하기
        int durationInSeconds = (int)grab.getVideoTrack().getMeta().getTotalDuration();
		//동영상의 0초 이미지 잘라내기
        Picture thumbnail=grab.getNativeFrame();
        BufferedImage bufferedImage = AWTUtil.toBufferedImage(thumbnail);
        //잘라낸 이미지의 해상도 추출하기(동영상의 해상도를 직접 알아낼 수 있는 방법이 없기에 사진을 잘라 해상도를 알아냄)
        int width= (int) (bufferedImage.getWidth()/this.imageThumbRatio);
        int height= (int) (bufferedImage.getHeight()/this.imageThumbRatio);
        
        //저장하려는 경로가 없다면 생성
        File folder=new File(streamingThumbnailPath+File.separator+media.getFileNameWithoutExtension());
        if (!folder.exists())
            folder.mkdirs();
        //모든 쓰레드 작업들을 담을 배열 생성
        ArrayList<Thread> threadList=new ArrayList<Thread>();
        //초단위로 잘라내야하기 때문에 time을 1간격으로 동영상 재생시간만큼 반복
        for(int time=0;time<=durationInSeconds;time++) {
        	File saveThumbnailPath=new File(folder,time+"."+this.thumbnailImageFormat);
            Runnable r = new ThreadStreamImage(time,saveThumbnailPath,source,width,height,this.thumbnailImageFormat);
            Thread thread = new Thread(r);   
            //쓰레드 작업을 배열에 추가 및 시작
            threadList.add(thread);
            thread.start();
        }
        //모든 작업이 끝날떄까지 대기
        for(Thread thread:threadList) {
        	thread.join();![](https://velog.velcdn.com/images/tjwjdgus83/post/5fb911b6-319d-4168-a461-f1562bec68f7/image.png)

}

비교

  • singleThread code
public class FileUtilImpl implements FileUtil{
	private void generateVideoStreamingThumbnail(File source,MediaDto media) throws Exception{
		FrameGrab grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(source));
        int durationInSeconds = (int)grab.getVideoTrack().getMeta().getTotalDuration();

        Picture thumbnail=grab.getNativeFrame();
        BufferedImage bufferedImage = AWTUtil.toBufferedImage(thumbnail);
        
        int width= (int) (bufferedImage.getWidth()/this.imageThumbRatio);
        int height= (int) (bufferedImage.getHeight()/this.imageThumbRatio);
        
        File folder=new File(streamingThumbnailPath+File.separator+media.getFileNameWithoutExtension());
        if (!folder.exists())
            folder.mkdirs();
        
        for(int time=0;time<=durationInSeconds;time++) {
			grab.seekToSecondPrecise(time);
			thumbnail=grab.getNativeFrame();
        	bufferedImage = AWTUtil.toBufferedImage(thumbnail);
        	Thumbnails.of(bufferedImage)
            .size(width,height)
            .outputFormat(thumbnailImageFormat)
            .toFile(new File(folder,time+"."+this.thumbnailImageFormat));
        }
	}
}
  • 성능

    • singleThread

      메모리:88MB

      시간:4.3s

    • multiThread

      메모리:295MB

      시간:1.4s

주의점

  • 멀티쓰레드는 여러개의 쓰레드를 생성하기 때문에 메모리를 더 많이 소모한다.따라서 java.lang.OutOfMemoryError와 같은 에러를 만날 수 있다.이를 미리 방자하기 위해 멀티쓰레드 방식을 선택했을 경우와 싱글쓰레드 방식을 선택했을 경우를 나눠 예상 사용 메모리를 미리 계산하고 현재 가용 가능한 메모리보다 크다면 싱글쓰레드,작다면 멀티쓰레드 방식으로 분기처리할 필요성이 있다.
profile
숲을 보는 코더

0개의 댓글