求助,java多线程安全问题问题,系统整个当掉

  很多人会对Python进程、线程、协程有那么些难以区分,特别是什么样的情况下,是什么样的状况,还有些朋友是在状态外的,所以一定要进行仔细的区分,赶紧跟小编一起来看看吧。
  进程和线程的解释
  进程(process)和线程(thread)是操作系统的基本概念,计算机的核心是CPU,它承担了所有的计算任务;
  单个CPU一次只能运行一个任务,代表单个CPU总是运行一个进程,其他进程处于非运行状态;
  一个进程有且最少包括一个线程;
  一个进程内存空间是共享的,每个线程可以使用这个共享内存;一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存;
  防止其它进程占用某一块内存区域使用互斥锁(Mutual exclusion,缩写 Mutex);
  保证多个线程不会互相冲突使用&信号量&(Semaphore);mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
  操作系统的设计,因此可以归结为三点:
  (1)以多进程形式,允许多个任务同时运行;
  (2)以多线程形式,允许单个任务分成不同的部分运行;
  (3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
  进程解释
  狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
  广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
  进程的概念主要有两点:
  第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
  第二,进程是一个&执行中的程序&。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
  进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
  线程解释
  线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
  进程和线程的区别?
  进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
  1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
  3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
  线程和进程的优缺点
  线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
  Python GIL(Global Interpreter Lock)
  In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython&s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
  上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?
  首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。
  Python threading模块
  线程有2种调用方式
  简单的调用方式
  自定义类的调用方式
  简单调用方式:自定义一个继承threading.Thread的子类,通过自定义类的对象调用
  import threading
  import time
  def f1(n):
  print(&time time time ...&,n)
  time.sleep(5)
  t = threading.Thread(target=f1,args=(123,))
  t1 = threading.Thread(target=f1,args=(456,))
  t.start()
  t1.start()
  2.自定义类的调用方式
  class Mythread(threading.Thread):
  def __init__(self,num):
  threading.Thread.__init__(self)
  self.num = num
  def run(self):
  print(&running on number:%s& % self.num)
  time.sleep(3)
  if __name__ == '__main__':
  for i in range(20):
  t1 = Mythread(i)
  t1.start()
  Join & Daemon
  def run(n):
  print(&[%s]-------running-----\n& % n)
  time.sleep(2)
  print('----done----')
  def main():
  for i in range(5):
  t = threading.Thread(target=run,args=[i,])
  t.start()
  t.join(1)
  print('start thread', t.getName())
  m = threading.Thread(target=main,args=[])
  m.setDaemon(True)
  m.start()
  m.join(timeout=2)
  print(&-----main thread done----&)
  更多的方法:
  start 不代表当前线程并不会立即被执行,而是等待CPU调度,(准备就绪,等待调度)
  setName 为线程设置名称
  setDaemon(True) True表示主线程不等待子线程,执行完自己的任务后,自动关闭,子线程有可能未执行完毕。(默认情况下,主线程要等待子线程执行完毕后再关闭主线程),(True:后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;False:前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止)
  join(2) 如果不想让线程并发的操作,表示主线程到此等待,等待直到子线程执行完毕。如果加上参数,表示主线程在此最多等几秒。该方法使得多线程变得无意义
  run 线程被cpu调度后自动执行线程对象的run方法
  线程锁(互斥锁Mutex)
  def addNum():
  global num #在每个线程中都获取这个全局变量
  print('--get num:',num )
  time.sleep(1)
  lock.acquire() #修改数据前加锁
  num -=1 #对此公共变量进行-1操作
  lock.release() #修改后释放
  num = 100 #设定一个共享变量
  thread_list = []
  lock = threading.Lock() #生成全局锁
  for i in range(100):
  t = threading.Thread(target=addNum)
  t.start()
  thread_list.append(t)
  for t in thread_list: #等待所有线程执行完毕
  t.join()
  print('final num:', num )
  RLock(递归锁)
  说白了就是在一个大锁中还要再包含子锁
  import threading,time
  def run1():
  print(&grab the first part data&)
  lock.acquire()
  global num
  num +=1
  lock.release()
  return num
  def run2():
  print(&grab the second part data&)
  lock.acquire()
  global num2
  num2+=1
  lock.release()
  return num2
  def run3():
  lock.acquire()
  res = run1()
  print('--------between run1 and run2-----')
  res2 = run2()
  lock.release()
  print(res,res2)
  if __name__ == '__main__':
  num,num2 = 0,0
  lock = threading.RLock()
  for i in range(10):
  t = threading.Thread(target=run3)
  t.start()
  while threading.active_count() != 1:
  print(threading.active_count())
  print('----all threads done---')
  print(num,num2)
  Semaphore(信号量)
  互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
  import threading,time
  def run(n):
  semaphore.acquire()
  time.sleep(1)
  print(&run the thread: %s\n& %n)
  semaphore.release()
  if __name__ == '__main__':
  num= 0
  semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
  for i in range(20):
  t = threading.Thread(target=run,args=(i,))
  t.start()
  while threading.active_count() != 1:
  pass #print threading.active_count()
  print('----all threads done---')
  print(num)
主讲:马士兵类型:初级教程
主讲:高淇类型:JAVA
主讲:高淇、裴新类型:初级教程
主讲:陈博类型:高级教程
主讲:陈博类型:高级教程
微信公众号他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)QQ空间掉帧率优化实战&&
作者:邓荣欣, 腾讯移动客户端开发工程师
商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。
原文链接:
WeTest 导读
空间新业务需求日益增多,在业务开发阶段的疏忽,或者是受到其他业务的影响(比如一些非空间的业务网络回包或者逻辑在主线程进行),导致空间的某些页面掉帧率上升。
本文从两个方向介绍优化掉帧率:
● Time Profiler时间分析工具
● 一些优化手段
Time Profiler(Xcode 9.1)
Time Profiler分析原理:它按照固定的时间间隔来跟踪每一个线程的堆栈信息,通过统计比较时间间隔之间的堆栈状态,来推算某个方法执行了多久,并获得一个近似值。
下面是Time Profiler的界面说明和一些使用建议。
1、主线程使用波峰
开始模拟用户使用App的时候,可以看到主线程的使用情况,它的波峰会忽高忽低,说明app正在进行耗时计算/正常计算,我们可以截取不同时间段的波峰区间进行探究,比如刚进入空间的5秒内,或者拉取到新feeds流之后平缓的5秒等不同场景(小tips:使用触控板向左右两边挪动可以进一步细化时间区间)
2、关于筛选面板的使用
● Separate by State:此选项会根据应用程序的生命周期状态对结果进行分组,并且是检查应用程序在多大程度上执行以及何时执行的有用方法。
● Separate by Thread:根据线程类别分开,方便查看哪些线程占用了最大的CPU。
● Invert Call Tree:调用树倒返过来,将习惯性的从根向下一级一级的显示,如选上就会返过来从最底层调用向一级一级的显示。如果想要查看那个方法调用为最深时使用会更方便些。
● Hide System Libraries:选上它只会展示与应用有关的符号信息,一般情况下我们只关心自己写的代码所需的耗时,而不关心系统库的CPU耗时。(但是很多我们的代码往往是由系统的函数进来,隐藏的话往往可能会丢失很重要的信息)
● Flatten Recursion:将递归函数视为每个堆栈跟踪中的一个条目,而不是多个。
● Top Functions:将花费在函数中的总时间视为直接在该函数内的时间总和以及该函数所调用的函数花费的时间。如果函数A调用B,那么A的时间被报告为A的时间并且加上在B中花费的时间。
为了可以获取更多的信息,建议只勾选 “Separate by Thread”
3、操作小技巧
在折叠的堆栈,按住“alt”点开旁边的三角形即可展开全部折叠堆栈,如果发现耗时严重的堆栈中,可以右键点开菜单,选中“Reveal in Xcodes”即可跳转到对应的代码区域 。
在好友动态页面来回滑动,笔者分四种情况来模拟用户的使用习惯:
● 刚进入空间(无缓存),下拉刷新
● 刚进入空间(有缓存),下拉刷新
● 来回滑动
● 上拉加载更多
1、将耗时操作(如文件IO)放到工作线程
在我们读取Gif首帧的时候,-[QZoneGIFDecode firstFrameWithURL:viewSize:]里面是有一部是从磁盘里读取二进制文件并且转换成NSData,然后再进行解码,这部分的IO操作优化后是放到了工作线程,异步读取完成解码之后再展示图片,不阻塞主线程。
再举一个例子:异步解析网络回包数据和异步排版
将耗时操作放到工作线程异步执行占了优化工作的大头,在这个过程中,要注意多线程问题,比如线程安全问题、死锁、野指针等,比如容器类的读写操作,最常用的NSMutableArray, NSMutableDictionary等, 这类集合在调用读写方法时并不是线程安全,简单地在里面进行加锁操作是可以保证线程安全,不过也可能会导致其他耗时问题。
2、耗时函数优化
上图堆栈表示的是展示图片, 整个流程如下:
由于空间里面存在大部分图片,其中走网络下载的图片就是上述这个流程。在这个过程中,刨开网络下载的部分,我们会根据图片URL来存取。存取过程首先会将URL 进行MD5加密之后作为Key来进行存取,其实这一步不是必要的,而且系统提供的MD5函数比较耗时。
优化手段:
优化缓冲池存取过程,直接使用URL作为Key来存取,去掉MD5调用。
3、减少- (void)scrollViewDidScroll:
(UIScrollView *)scrollView 这个函数里面的耗时操作
这个方法在任何方式触发 contentOffset 变化的时候都会被调用(包括用户拖动,减速过程,直接通过代码设置等),可以用于监控 contentOffset 的变化,并根据当前的 contentOffset 对其他 view 做出随动调整。但是这个方法在滚动的时候每秒调用上百次,如果在里面加入耗时操作就可能对掉帧率造成很大影响。
解决方法:优化调用耗时,或者将耗时操作放到别的地方去
4、提前进行(耗时操作不可避免)
在进入空间之前,我们会有很多初始化工作,比如初始化用户的空间装扮,读取用户的一些配置等,有时候还会涉及IO操作,这部分的耗时是必不可免的。为了保证用户的体验问题,进入空间前,我们可以提前初始化(preload),将一些耗时操作选择在适当的时机提前进行。
在业务上,我们会读取一些设置项来展示或者进行不同的功能,这些选项的即时读取可能是非常耗时的(尤其是涉及非线程安全容器的读取,里面往往是利用了互斥锁或者信号量等机制保证线程安全,耗时就更加严重),我们可以使用静态变量和dispatch_once来保存起来,避免每次都去要读取一遍。
再举一个例子:
我们在渲染的时候会用到很多字体颜色,每次创建这些新的字体和颜色也是耗时的一部分,我们可以将这些UIColor和UIFont缓存起来,过程如下图:
这里还可以做进一步的优化,就是在进入空间前,把常用的字体生成并且缓存起来,减少渲染时再生成的耗时。
6、减少不必要的操作
为了方便回溯用户的操作行为,我们会在App里面加上很多log,一般log都涉及IO操作,不是必要的log我们要减少,尽量只在关键点打log。
7、懒加载view
不要在cell里面嵌套太多的view,这会很影响滑动的流畅感,而且更多的view也需要花费更多的CPU跟内存。假如由于view太多而导致了滑动不流畅,那就不要在一次就把所有的view都创建出来,把部分view放到需要显示cell的时候再去创建。比如:
8、利用主线程不同的runloop
优化缓冲池存取过程,直接使用URL作为Key来存取,去掉MD5调用。
上图是进入空间的时候,需要初始化混合Cover挂件的耗时问题。
我们可以利用不同的runloop来优化这个耗时问题。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当我们初始化挂件并加到 DefaultMode 时,这个事件 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 初始化挂件函数就不会被回调,因而也不会影响到滑动操作。
很多App会用到这个点来进行优化,比如我们操作微信的时候,在朋友圈上拉加载更多的时候,如果不松开手是不会加载出来的,只有放开手才会展示出更多的feeds,也是利用了不同的runloop。
解决掉帧的方法还有很多,希望本文能提供给大家一些思路,后续会继续更新。
一款针对Unity游戏/产品的深度性能分析工具,由腾讯WeTest和unity官方共同研发打造,可以帮助游戏开发者快速定位性能问题。旨在为游戏开发者提供更完善的手游性能解决方案,同时与开发环节形成闭环,保障游戏品质。
目前,限时内测正在开放中,即日起至,所有预约成功的WeTest平台认证用户,均可以免费、不限次数地使用最完整的UPA服务,点击 立即预约。
对UPA感兴趣的开发者,欢迎加入QQ群:
如果对使用当中有任何疑问,欢迎联系腾讯WeTest企业QQ:
被转藏 : 1次
被转藏 : 1次联系方式请留下您的联系方式方便我们沟通确认必填项,请输入正确的QQ号必填项,请输入正确的手机号必填项,请输入正确的邮箱确定亲,要输入内容才能提交哦~感谢您的支持,我们会尽快核实~}

我要回帖

更多关于 多线程安全问题 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信