比方电脑里你安装的qq,暴风,迅雷他们都可以单独独立运行,那么我们就可以说qq是一个进程,暴风也是一个进程,迅雷更是一个进程
而要说到什么是线程,线程是运行在进程里里的程序
举例qq(线程),我们能同时用qq软件进行聊天,听音乐,为什么呢,这就是线程的优点所在,单独运行但又不互相干扰,创建线程有两种方法,线程一定要复写run方法,用start启动线程
使用Thread创建线程:
public MyThread extends Thread{
public void run(){
for (int i=0i<20i++){
System.out.println(
“my Thread i value: ”+i)
}
使用Runnable创建线程:
public MyThread implements Runnable{
public void run(){
for (int i=0i<20i++){
System.out.println(
“my Thread i value: ”+i)
}
对于这两种方法,实现线程,但是一般情况下我们都用第二种
因为第一种是用继承的关系,而第二种为实现接口
但是相对于第一种,第二种我们还可以另外继承类,来扩展功能,所以编程序时比较趋向于用实现的方法写线程
给你一个完整的例子:
class MyThread implements Runnable{
private int ticket = 5 // 假设一共有5张票
public void run(){
for(int i=0i<100i++){
if(ticket>0){ // 还有票
System.out.println(Thread.currentThread().getName()+"卖票:ticket = " + ticket-- )
}
}
}
}
public class SyncDemo01{
public static void main(String args[]){
MyThread mt = new MyThread() // 定义线程对象
Thread t1 = new Thread(mt) // 定义Thread对象
Thread t2 = new Thread(mt) // 定义Thread对象
Thread t3 = new Thread(mt) // 定义Thread对象
t1.start()
t2.start()
t3.start()
}
}
这是一个三线程:
三个线程都start,所以他们可以同时运行,但是又由于电脑cpu只有一个,只能运行一个线程,那么这三个线程就会去抢,谁抢到谁就线运行,start表示开始运行run方法,导致三个线程共享5个车票,你会发现每次运行结果不一样,这就是抢的结果,如果把车票写在run方法里就不一样了
Thread-0卖票:ticket = 5
Thread-0卖票:ticket = 4
Thread-0卖票:ticket = 3
Thread-2卖票:ticket = 2
Thread-2卖票:ticket = 1
结果二:
Thread-0卖票:ticket = 5
Thread-0卖票:ticket = 4
Thread-0卖票:ticket = 3
Thread-1卖票:ticket = 2
Thread-0卖票:ticket = 1
反正很多答案,自己试。
Linux系统中,实现线程同步的方式大致分为六种,其中包括:互斥锁、自旋锁、信号量、条件变量、读写锁、屏障。最常用的线程同步方式就是互斥锁、自旋锁、信号量:
1、互斥锁
互斥锁本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,当互斥锁由某个线程持有后,这个互斥锁会锁上变成lock状态,此后只有该线程有权力打开该锁,其他想要获得该互斥锁的线程都会阻塞,直到互斥锁被解锁。
互斥锁的类型:
①普通锁:互斥锁默认类型。当一个线程对一个普通锁加锁以后,其余请求该锁的线程将形成一个等待队列,并在锁解锁后按照优先级获得它,这种锁类型保证了资源分配的公平性。一个线程如果对一个已经加锁的普通锁再次加锁,将引发死锁对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再次解锁,将导致不可预期的后果。
②检错锁:一个线程如果对一个已经加锁的检错锁再次加锁,则加锁 *** 作返回EDEADLK对一个已经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁,则解锁 *** 作返回EPERM。
③嵌套锁:该锁允许一个线程在释放锁之前多次对它加锁而不发生死锁其他线程要获得这个锁,则当前锁的拥有者必须执行多次解锁 *** 作对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁 *** 作返回EPERM。
④默认锁:一个线程如果对一个已经解锁的默认锁再次加锁,或者对一个已经被其他线程加锁的默认锁解锁,或者对一个解锁的默认锁解锁,将导致不可预期的后果这种锁实现的时候可能被映射成上述三种锁之一。
【老男孩教育】Linux运维云计算课程汇集了虚拟化、云计算、安全攻防、Python开发、SRE等技术,课堂效率高、内容丰富全面,由浅入深,循序渐进,帮助学员稳扎稳打,夯实基础,在有限的时间内帮助学员高效提升,成为符合企业需求的技术型人才。
2、自旋锁
自旋锁顾名思义就是一个死循环,不停的轮询,当一个线程未获得自旋锁时,不会像互斥锁一样进入阻塞休眠状态,而是不停的轮询获取锁,如果自旋锁能够很快被释放,那么性能就会很高,如果自旋锁长时间不能够被释放,甚至里面还有大量的IO阻塞,就会导致其他获取锁的线程一直空轮询,导致CPU使用率达到100%,特别CPU时间。
3、信号量
信号量是一个计数器,用于控制访问有限共享资源的线程数。
LinuxThread的线程机制LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy (Xavier.Leroy@inria.fr) 负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的 管理在核外函数库中实现。
1.线程描述数据结构及实现限制
LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量 __pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全 局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用 __pthread_main_thread表征__pthread_manager_thread的父线程(初始为 __pthread_initial_thread)。
struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它 一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、 p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列 pthread_create()之后形成的__pthread_handles数组将如下图所示:
图2 __pthread_handles数组结构
新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。
LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在 LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中 在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:
每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的 *** 作数,LinuxThreads与POSIX一致,定义 PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024 (PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字节)。
2.管理线程
"一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads 中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建 (__clone())并启动管理线程。
在一个进程空间内,管理线程与其他线程之间通过一对"管理管道(manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动 了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和 __pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求, 但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone ()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。
创建管理线程的流程如下所示:
(全局变量pthread_manager_request初值为-1)
图3 创建管理线程的流程
初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id, 2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的 子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子 线程的概念应该当作子进程来理解。)
__pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为 timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出 整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。
然后才是读取管道中的请求,根据请求类型执行相应 *** 作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。
3.线程栈
在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。
用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属 性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同 时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK 方式和用户自定义方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如 果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图:
图4 栈结构示意
低地址被保护的页面用来监测栈溢出。
对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。
不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。
4.线程id和进程id
每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)