
为了让代码能够并发执行,向创建线程并在核实的时候销毁它。
由于目的比较单纯,只是讲解基础的线程创建方法,所以可以直接使用threading库中的Thread类来实例化一个线程对象。
例子,用户输入两个数字,并且求其两个数字的四则运算的结果:
除了以上的一些功能以外,在python线程
中没有其他的诸如给线程发信号、设置线程调度属性、执行任何其他高级 *** 作的功能了,如果需要这些功能,就需要手工编写了。
另外,需要注意的是,由于GIL(全局解释器锁)的存在,限制了在python解释器当中只允许运行一个线程。基于这个原因,不停该使用python线程来处理计算密集型的任务,因为在这种任务重我们希望在多个CPU核心上实现并行处理。Python线程更适合于IO处理以及设计阻塞 *** 作的并发执行任务(即等待IO响应或等待数据库取出结果等)。
如何判断线程是否已经启动?
目的:我们加载了一个线程,但是想要知道这个线程什么时候才会开始运行?
方法:
线程的核心特征我认为就是不确定性,因为其什么时候开始运行,什么时候被打断,什么时候恢复执行,这不是程序员能够控制的,而是有系统调度
来完成的。如果遇到像某个线程的运行依托于其他某个线程运行到某个状态时该线程才能开始运行,那么这就是线程同步
问题,同样这个问题非常棘手。要解决这类问题我们要借助threading中的Event对象。
Event其实和条件标记类似,匀速线程
等待某个时间发生。初始状态时事件被设置成0。如果事件没有被设置而线程正在等待该事件,那么线程就会被阻塞,直到事件被设置位置,当有线程设置了这个事件之后,那么就会唤醒正在等待事件的线程,如果线程等待的事件已经设置了,那么线程会继续执行。
一个例子:
如上能够确定的是,主线程会在线程t运行结束时再运行。
hashmap生成的链表在jdk18之前是插入头部的,在jdk18中是插入尾部的。
至于为什么要插入到头部,因为头部最快啊,不需要遍历到尾部,直接改变就行
还有就是插入尾部容易产生环形链表,如果 条件竞争(指多个线程或者进程在读写一个共享数据时结果依赖于它们执行的相对 时间 的情形) 发生了,可 能会出现环形链表,之后当我们get(key) *** 作时,就有可能发生死循环。
但是在JDK18之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
JDK18使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(nlogN)提高了效率)
1HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。
2ConcurrentHashMap采用锁分段技术, 将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment ,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。
3ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。
那是因为你线程池没有关闭,所以主线程就不会退出,需要主线程退出只需要调用线程池的shutdown()方法,你的代码可以在main方法的最后一行加上 thrshutdown(); 就可以了。
编程对于很多人来说,还是比较好上手的。当你学会了一门语言,可以编写一些程序了,很快就会遇到一道坎,并发编程,单线程下好好跑着的程序,怎么就运行异常了?怎么就得不到期望的结果。在面试中,并发编程也是经常出现,我们今天来讨论一个问题, 并发编程,容易出哪些问题?
相信大家在学习并发编程的时候,都会遇到这样一个经典问题,有一个函数,执行i=i+1,执行1000遍。在单线程的环境下,得到的结果都是预期的1000。如果是两个线程同时运行,那么,得到的结果可能是2000,也有可能小于2000。
这是因为i=i+1不是一个原子 *** 作,我们会获取i的值,然后执行一次加法运算,最后将结果赋值给i。当多个线程执行的时候,获取到的i的值之后,在执行后面的动作之前,另外一个线程已经修改了i,造成最终的结果小于2000。
并发编程的时候,我们往往无法确定多个线程之间的执行顺序,经常我们会出现这样一个错误。我们在一个线程中使用的变量,会在另外一个线程中进行初始化或者赋值。最常见的,便是我们在主线程上创建一个子线程,然后再进行变量的初始化,子线程的执行可能早于父线程,造成程序错误。
为了解决并发问题,我们通常会引入锁、信号、信号量等手段来保证临界区只会被一个线程访问,或者让一个线程等待另外一个线程执行完成。但是,引入锁之后,我们又可能会出现死锁的问题,例如:线程1与线程2都需要抢占AB两把锁,假设线程1先抢占了A锁,线程2抢占了B锁,这个时候,线程1在等待B锁,线程2在等待A锁,就这样,等到海枯石烂,我们称之为死锁。
出现死锁,需要同时满足下面几个条件:1线程需要对需要的资源进行互斥访问(例如一个线程抢到锁)2持有并等待(例如一个线程抢到了A锁,然后在等待B锁)3非抢占(线程抢到了锁之后,不能被其他线程抢到)4循环等待,线程之前存在一个环路。
那么,如何解决死锁的问题呢?既然出现死锁需要同时满足上面的几个条件,那么,我们只要破坏其中一个条件,就能够避免死锁问题。
今天,我们了解到并发编程带来的问题与解决方案,希望对你在平时的工作或者面试有所帮助。
import javautilconcurrentBrokenBarrierException;
import javautilconcurrentCyclicBarrier;
/
单独的计算线程,比如计算{110}的相加
@author zhaohb
/
public class CounterThread extends Thread{
private int start;
private int end;
private CyclicBarrier barrier ;
public CounterThread(int id,int start, int end,CyclicBarrier barrier) {
thisstart = start;
thisend = end;
thisbarrier = barrier;
setName("Thread-"+id+" ");
}
@Override
public void run() {
int count = 0;
for(int i=start;i<end+1;i++){
count += i;
}
CountertotalCount(count);
try {
barrierawait();
} catch (InterruptedException e) {
eprintStackTrace();
} catch (BrokenBarrierException e) {
eprintStackTrace();
}
}
}
import javautilconcurrentCyclicBarrier;
import javautilconcurrentExecutorService;
import javautilconcurrentExecutors;
/
测试类入口
@author zhaohb
/
public class CounterTest {
public static void main(String[] args) {
//CyclicBarrier指定了当10个线程运行结束时候,可以进行最后结果展示了
CyclicBarrier barrier = new CyclicBarrier(10,new TotalTask(new Counter()));
ExecutorService executorService = ExecutorsnewCachedThreadPool();
for(int i=0;i<10;i++){
int start = i10+1;
int end = start + 9;
CounterThread counterThread = new CounterThread(i,start, end,barrier);
executorServiceexecute(counterThread);
}
executorServiceshutdown();
}
}
/
线程结果计算:将单独的线程的计算的结果相加,汇总的到总的结果
@author zhaohb
/
class Counter {
private static int count =0;
public synchronized static int totalCount(int perCount){
count += perCount;
return count;
}
public int totalResult(){
return count;
}
}
/
最后结算展示线程
@author zhb
/
class TotalTask implements Runnable{
private Counter counter ;
public TotalTask(Counter counter){
thiscounter = counter;
}
@Override
public void run() {
Systemoutprintln("所有线程运行完毕,总结果为:");
int total = countertotalResult();
Systemoutprintln(total);
}
}
一个经典问题,i初始值为0,开启10个线程,每个线程对i循环1000次进行++ *** 作,结果却小于等于10000。
出现这种问题的原因是,当多个线程对同一个共享变量进行 *** 作时,由于 *** 作系统底层高速缓存的存在,会出现缓存不一致,即一个线程对变量 *** 作后,没有立即同步到主存,其他线程从自己的工作缓存取值进行 *** 作,就会导致最终结果小于等于预期值。
可以使用 synchronized关键字,每次只有一个线程获取锁执行i++ *** 作,但是这种相当于串行,性能较低。
每次执行结果都是10000。
atomic包下的原子类,底层使用了Unsafe类,其内存语义是,对变量的++ *** 作是原子性的,不可分割的,且value是volatile修饰的,实现一个线程对变量++ *** 作后,会将其他线程的工作内存的共享变量的缓存行失效,且立即同步到主存,其他线程再取值就会从主存去取了。需要注意的是,volatile本身并不能实现原子性。
atomic底层使用了CAS *** 作,比较并替换,类似乐观锁机制,每次先查询,然后更新的时候会比较当前值是不是自己当初查询的那个值,如果不是,就再次查询,再次更新。
初始值是0,线程1先查询,值为0,然后CPU上下文切换,线程2这时也查询值为0,更新为1,然后又更新为初始值0,此时线程1再CAS更新,先查询发现值等于当初自己查询的值0,所以就更新成功了,但是其实中间已经被线程1更新过一次了。
对于ABA问题,可以使用AtomicStampedReference类来解决,每次修改记录一个版本号。
因为CAS *** 作每次都是先查询再更新,如果并发量比较高,可能会导致某个线程一直CAS失败,就一直重试,从而影响性能。
类似于ConcurrentHashMap类的思想,分段加锁,有一个base初始值,如果并发量不高的情况下,直接对base进行CAS *** 作,如果失败,会初始化一个Cell数组,之后每个线程维护了一个本地变量probe,与Cell数组的大小进行哈希取模,这样一个线程就映射到一个元素,对数组内元素CAS *** 作失败的话,可以重新对probe赋值,如果还是失败,会对Cell数组扩容,每次扩容为原来的2倍大小。
最后需要对base值和Cell数组的每个元素相加,得到最终结果。
注意,这里base和Cell数组都是volatile修饰的,也就是一旦一个线程对其修改后,对其他线程立即可见。
以上就是关于Python中级精华-并发之启动和停止线程全部的内容,包括:Python中级精华-并发之启动和停止线程、HashMap线程不安全ConcurrentHashMap线程安全为啥呢、Concurrent包中线程池问题:java中主函数执行完了为什么主线程还没有退出是因为啥都已经输出了横杠了.等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)