精华内容
下载资源
问答
  • 先提到线程同步是个什么,概念是什么,就是线程通讯中通过使用某种技术访问数据时,而一旦此线程访问到,其他线程也就不能访问了,直到该线程对数据完成操作才结束。 Event事件是一种实现方式:通过内部的标记看看...
     先提到线程同步是个什么,概念是什么,就是线程通讯中通过使用某种技术访问数据时,而一旦此线程访问到,其他线程也就不能访问了,直到该线程对数据完成操作才结束。
         Event事件是一种实现方式:通过内部的标记看看是不是变化,也就是true or false了,
         将set(),clear(),is_set(),为true,wait(timeout=None)此种设置true的时长,等到返回false,不等到超时返回false,无线等待为None,
    
         来看一个wait的使用:
    from threading import Event, Thread
    import logging
    
    logging.basicConfig(level=logging.INFO)
    
    def A(event:Event, interval:int):
        while not event.wait(interval): # 要么true  or false
            logging.info('hello')
    
    e = Event()
    Thread(target=A, args=(e, 3)).start()
    
    e.wait(8)
    e.set()
    print('end--------------')

    使用锁Lock解决数据资源在争抢,从而使资源有效利用。
    lock的方法:
    acquire(blocking=True,timeout=-1),默认阻塞,阻塞设置超时时间,非阻塞,timeout禁止使用,成功获取锁,返回True,否则False。
    有阻塞就有释放 ,解开锁,release(),从线程释放锁,上锁的锁重置为unloced未上锁调用,抛出RuntimeError异常。

    import threading
    from threading import Thread, Lock
    import logging
    import time
    
    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'

    logging.basicConfig(format=FORMAT, level=logging.INFO)

    
    cups = []
    lock = Lock()
    
    def worker(count=10):
        logging.info("I'm working for U.")
        flag = False
        while True:
        lock.acquire() # 获取锁
    
        if len(cups) >= count:
            flag = True
    
        time.sleep(0.0001) 
        if not flag:
            cups.append(1)
    
        if flag:
            break
    
    logging.info('I finished. cups = {}'.format(len(cups)))
    
    for _ in range(10):
        Thread(target=worker, args=(1000,)).start()

    使用锁的过程中,总是不经意加上锁,出现死锁的产生,出现了死锁,如何解决呢?
    使用try finally 将锁释放,另一种使用with上下文管理。
    锁的使用场景在于应该少用锁,还要就是如若上锁,将锁的使用时间缩短,避免时间太长而出现无法释放锁的结果。

    可重入锁Lock,

    
    import threading
    import time
    
    lock = threading.RLock()
    print(lock.acquire())
    print('------------')
    print(lock.acquire(blocking=False))
    print(lock.acquire())
    print(lock.acquire(timeout=3.55))
    print(lock.acquire(blocking=False))
    #print(lock.acquire(blocking=False, timeout=10)) # 异常
    lock.release()
    lock.release()
    lock.release()
    lock.release()
    print('main thread {}'.format(threading.current_thread().ident))
    print("lock in main thread {}".format(lock)) # 注意观察lock对象的信息
    lock.release()
    #lock.release() #多了一次
    print('===============')
    print()
    
    print(lock.acquire(blocking=False)) # 1次
    #threading.Timer(3, lambda x:x.release(), args=(lock,)).start() # 跨线程了,异常
    lock.release()
    print('~~~~~~~~~~~~~~~~~')
    print()
    
    # 测试多线程
    print(lock.acquire())
    
    def sub(l):
        print('{}: {}'.format(threading.current_thread(), l.acquire())) # 阻塞
        print('{}: {}'.format(threading.current_thread(), l.acquire(False)))
        print('lock in sub thread {}'.format(lock))
        l.release()
        print('sub 1')
        l.release()
        print('sub 2')
        # l.release() # 多了一次
    
    threading.Timer(2, sub, args=(lock,)).start() # 传入同一个lock对象
    print('++++++++++++++++++++++')
    print()
    
    print(lock.acquire())
    
    lock.release()
    time.sleep(5)
    print('释放主线程锁')
    lock.release()

    使用构造方法Condition(lock=None),默认是Rloc,
    具体方法为;
    acquire(*args),获取锁
    wait(self,timeout=None),等待或超时
    notify(n=1),唤醒线程,没有等待就没有任何操作,指线程
    notify_all(),唤醒所有等待的线程。
    Condition主要用于生产者和消费者模型中,解决匹配的问题。
    使用方式:先获取acquire,使用完了要释放release,避免死锁最好使用with上下文;生产者和消费者可以使用notify and notify_all。

    如下例子:

    from threading import Thread, Event
    import logging
    import random
    
    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT, level=logging.INFO)
    
    ## 此例只是为了演示,不考虑线程安全问题
    
    class Dispatcher:
        def __init__(self):
            self.data = None
            self.event = Event() # event只是为了使用方便,与逻辑无关
    
        def produce(self, total):
            for _ in range(total):
                data = random.randint(0,100)
                logging.info(data)
                self.data = data
                self.event.wait(1)
                self.event.set()
    
        def consume(self):
            while not self.event.is_set():
                data = self.data
                logging.info("recieved {}".format(data))
                self.data = None
                self.event.wait(0.5)
    
    d = Dispatcher()
    p = Thread(target=d.produce, args=(10,), name='producer')
    c = Thread(target=d.consume, name='consumer')
    c.start()
    p.start()

    这里代码会有缺陷:优化如下:

    from threading import Thread, Event, Condition
    import logging
    import random
    
    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT, level=logging.INFO)
    
    ## 此例只是为了演示,不考虑线程安全问题
    
    class Dispatcher:
        def __init__(self):
            self.data = None
            self.event = Event() # event只是为了使用方便,与逻辑无关
            self.cond = Condition()
    
        def produce(self, total):
            for _ in range(total):
                data = random.randint(0,100)
                with self.cond:
                    logging.info(data)
                    self.data = data
                    self.cond.notify_all()
                    self.event.wait(1) # 模拟产生数据速度
                    self.event.set()
    
        def consume(self):
            while not self.event.is_set():
                with self.cond:
                    self.cond.wait() # 阻塞等通知
                    logging.info("received {}".format(self.data))
                self.event.wait(0.5) # 模拟消费的速度
    
    d = Dispatcher()
    p = Thread(target=d.produce, args=(10,), name='producer')
    # 增加消费者
    
    for i in range(5):
        c = Thread(target=d.consume, name='consumer-{}'.format(i))
        c.start()
    p.start()

    Barrier的使用:
    方法如下:
    Barrier(parties, action=None,
    timeout=None),构建barrier对象,timeout未指定的默认值;
    n_waiting ,当前barrier等待的线程数。;
    parties ,需要等待
    wait(timeout=None),wait方法设置超时并超时发送,barrie处于broken状态。
    而broken的状态方法:
    broken,打开状态,返回true;
    abort(),barrier在broken状态中,wait等待的线程会抛出BrokenBarrierError异常,直到reset恢复barrier;
    reset(),恢复barrier,重新开始拦截。
    barrier不做演示:

    还有semaphore信号量,每次acquire时,都会减一,到0时的线程再到release后,大于0,恢复阻塞的线程。
    方法:
    Semaphore(value=1) 构造方法,alue小于0,抛ValueError异常;
    acquire(blocking=True, timeout=None) 获取信号量,计数器减1,获取成功返回True;
    release() 释放信号量,计数器加1。
    使用信号量处理时,需要注意release超界问题,边界问题,其实,在使用中,python有GIL的存在,有的多线程就变成线程安全的,注意一点,但实际上它们并不是线程安全类型。因此我们在使用中要具体场景具体分析具体使用。

    转载于:https://blog.51cto.com/13890970/2327927

    展开全文
  • Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。...内核中采用的同步技术:  中断屏蔽 原子操作
    Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。
    

     

    Linux内核是多进程、多线程的操作系统,它提供了相当完整的内核同步方法。内核同步方法列表如下:

    =========================

    内核中采用的同步技术:    

    中断屏蔽

    原子操作  (分为整数原子操作和位 原子操作)

    信号量  (semaphore)

    RCU  (read-copy-update)

    SMP系统中的同步机制 :
    自旋锁 (spin lock)

    读写自旋锁

    顺序锁         (seqlock 只包含在2.6内核中)

    读写信号量  (rw_semaphore)

    大内核锁BKL(Big Kernel Lock)

    Seq锁()。

    ==========================

    一、概念介绍

     

    并发与竞态:

              并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。

    linux中,主要的竞态发生在如下几种情况:

    1、对称多处理器(SMP)多个CPU                特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。

    2、CPU内进程与抢占它的进程

    3、中断(硬中断、软中断、Tasklet、底半部)与进程之间

    只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生

    如果中断处理程序访问进程正在访问的资源,则竞态也会会发生。

    多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。 

    解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源的时候,其他的执行单元都被禁止访问。 

    访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,如:中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。 

    临界区和竞争条件:

    所谓临界区(critical regions)就是访问和操作共享数据的代码段,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样,如果两个执行线程有可能处于同一个临界区中,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions),避免并发和防止竞争条件被称为同步 

    死锁:

    死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了,所有线程都在相互等待,但它们永远不会释放已经占有的资源,于是任何线程都无法继续,这便意味着死锁的发生。

    =========================================================================================================================

    详细的同步方法如下:

    一、中断屏蔽

    CPU范围内避免竞态的一种简单方法是在进入临界区之前屏蔽系统的中断由于linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。

    中断屏蔽的使用方法:

    [html] view plaincopy
    1. local_irq_disable()//屏蔽中断  
    2. //临界区  
    3. local_irq_enable()//开中断  
    4. 特点:由于<span style="font-family:Times New Roman;">linux</span>系统的异步<span style="font-family:Times New Roman;">IO</span>,进程调度等很多重要操作都依赖于中断,在屏蔽中断期间所有的中断都无法得到处理,因此<strong>长时间的屏蔽是很危险</strong>的,有可能造成数据丢失甚至系统崩溃,这就要求在屏蔽中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。  

    中断屏蔽只能禁止本CPU内的中断,因此,并不能解决多CPU引发的竞态,所以单独使用中断屏蔽并不是一个值得推荐的避免竞态的方法,它一般和自旋锁配合使用。

     

     二、原子操作

    定义:原子操作指的是在执行过程中不会被别的代码路径所中断的操作。(原子原本指的是不可分割的微粒,所以原子操作也就是不能够被分割的指令)

    (它保证指令原子的方式执行而不能被打断)

    原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_settest_and_clear等指令用于临界资源互斥的原因。但是,在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以decl (递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。

    通俗理解:

          原子操作,顾名思义,就是说像原子一样不可再细分。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被OS的其他行为打断,是一个整体的过程,在其执行过程中,OS的其它行为是插不进来的。

    分类:linux内核提供了一系列函数来实现内核中的原子操作,分为整型原子操作位原子操作共同点是:在任何情况下操作都是原子的,内核代码可以安全的调用它们而不被打断。

     

    原子整数操作:

    针对整数的原子操作只能对atomic_t类型的数据进行处理,在这里之所以引入了一个特殊的数据类型,而没有直接使用C语言的int型,主要是出于两个原因:

    第一、让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用,同时,这也确保了该类型的数据不会被传递给其它任何非原子函数;

    第二、使用atomic_t类型确保编译器不对相应的值进行访问优化——这点使得原子操作最终接收到正确的内存地址,而不是一个别名,最后就是在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。

    原子整数操作最常见的用途就是实现计数器。

    另一点需要说明原子操作只能保证操作是原子的,要么完成,要么不完成,不会有操作一半的可能,但原子操作并不能保证操作的顺序性,即它不能保证两个操作是按某个顺序完成的。如果要保证原子操作的顺序性,请使用内存屏障指令。

    atomic_tATOMIC_INIT(i)定义如下:

    [html] view plaincopy
    1. typedef struct { volatile int counter; } atomic_t;  
    2. #define ATOMIC_INIT(i)  { (i) }  

    =========================整数相关函数: ===========================================          

    [html] view plaincopy
    1. ATOMIC_INIT(int i)                                         //声明一个atomic_t变量并且初始化为i  
    2.          int atomic_read(atomic_t *v)                      //原子地读取整数变量v  
    3.          void atomic_set(atomic_t *v, int i)               //原子地设置v为i  
    4.          void atomic_add(int i, atomic_t *v)         //v+i  
    5.          void atomic_sub(int i, atomic_t *v)         //v-i  
    6.          void atomic_inc(atomic_t *v)                //v+1  
    7.          void atomic_dec(atomic_t *v)                //v-1  
    8.          int atomic_sub_and_test(int i, atomic_t *v)       //v-i, 结果等于0,返回真,否则返回假   
    9.            int atomic_add_negative(int i, atomic_t *v)       //原子地给v+i,结果是负数,返回真,否则返回假  
    10.            int atomic_dec_and_test(atomic_t *v)              //v-1, 结果是0,返回真,否则返回假  
    11.            int atomic_inc_and_test(atomic_t *v)              //v+1,如果是0,返回真,否则返回假  

    在你编写代码的时候,能使用原子操作的时候,就尽量不要使用复杂的加锁机制,对多数体系结构来讲,原子操作与更复杂的同步方法相比较,给系统带来的开销小,对高速缓存行的影响也小,但是,对于那些有高性能要求的代码,对多种同步方法进行测试比较,不失为一种明智的作法。

     

    原子位操作:

    针对位这一级数据进行操作的函数,是对普通的内存地址进行操作的。它的参数是一个指针和一个位号。 

    =================================位操作相关函数:====================================

    [html] view plaincopy
    1. void set_bit(int nr, void *addr)                  //原子地设置addr所指对象的第nr位  
    2. void clear_bit(int nr, void *addr)                  //清空addr第nr位  
    3. void change_bit(int nr, void *addr)                 //反转addr第nr位  
    4. int test_and _set_bit(int nr, void *addr)           //设置addr第nr位,并返回原先的位的值  
    5. int test_and_clear_bit(int nr, void *addr)          //清除addr第nr位,并返回原先的值  
    6. int test_and_change_bit(int nr, void *addr)         //反转addr第nr,并返回原先的值  
    7. int test_bit(int nr, void *addr)                            //原子地返回addr的第nr位  

    为方便其间,内核还提供了一组与上述操作对应的非原子位函数,非原子位函数与原子位函数的操作完全相同,但是,非原子位函数不保证原子性,且其名字前缀多两个下划线。例如,与test_bit()对应的非原子形式是_test_bit(),如果你不需要原子性操作(比如,如果你已经用锁保护了自己的数据),那么这些非原子的位函数相比原子的位函数可能会执行得更快些。

     使用示例:

    创建和初始化原子变量                    
    atomic_t my_counter ATOMIC_INIT(0);    //声明一个atomic_t类型的变量my_counter 并且初始化为0
    或者  
    atomic_set( &my_counter, 0 );  

    [html] view plaincopy
    1. <span style="font-size:12px;color:#000000;">简单的算术原子函数                      
    2. val = atomic_read( &my_counter );      
    3. atomic_add( 1, &my_counter );      
    4. atomic_inc( &my_counter );      
    5. atomic_sub( 1, &my_counter );      
    6. atomic_dec( &my_counter );    
    7. </span>  

    三、自旋锁(spin lock

    自旋锁的引入:

    如果每个临界区都能像增加变量这样简单就好了,可惜现实不是这样,而是临界区可以跨越多个函数,例如:先得从一个数据结果中移出数据,对其进行格式转换和解析,最后再把它加入到另一个数据结构中,整个执行过程必须是原子的,在数据被更新完毕之前,不能有其他代码读取这些数据,显然,简单的原子操作是无能为力的(在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间),这就需要使用更为复杂的同步方法——锁来提供保护。 

    自旋锁的介绍:

    Linux内核中最常见的锁是自旋锁(spin lock),自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图获得一个被争用(已经被持有)的自旋锁,那么该线程就会一直进行忙循环旋转等待锁重新可用,要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行,在任意时间,自旋锁都可以防止多于一个的执行线程同时进入理解区,注意同一个锁可以用在多个位置例如,对于给定数据的所有访问都可以得到保护和同步。

    一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),所以自旋锁不应该被长时间持有,事实上,这点正是使用自旋锁的初衷,在短期间内进行轻量级加锁,还可以采取另外的方式来处理对锁的争用:让请求线程睡眠,直到锁重新可用时再唤醒它,这样处理器就不必循环等待,可以去执行其他代码,这也会带来一定的开销——这里有两次明显的上下文切换,被阻塞的线程要换出和换入。因此,持有自旋锁的时间最好小于完成两次上下文切换的耗时,当然我们大多数人不会无聊到去测量上下文切换的耗时,所以我们让持有自旋锁的时间应尽可能的短就可以了,信号量可以提供上述第二种机制,它使得在发生争用时,等待的线程能投入睡眠,而不是旋转。

    自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它们会导致睡眠),在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经持有的自旋锁,这样以来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行,这正是我们在前一章节中提到的双重请求死锁,注意,需要关闭的只是当前处理器上的中断,如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。 

    自旋锁的简单理解:

    理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为我当前正在运行,请稍等一会或者标记为我当前不在运行,可以被使用。如果A执行单元首先进入例程,它将持有自旋锁,当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入 

    自旋锁的API函数: 

    其实介绍的几种信号量和互斥机制,其底层源码都是使用自旋锁,可以理解为自旋锁的再包装。所以从这里就可以理解为什么自旋锁通常可以提供比信号量更高的性能。
    自旋锁是一个互斥设备,他只能会两个值:锁定解锁。它通常实现为某个整数之中的单个位。
    测试并设置的操作必须以原子方式完成。
    任何时候,只要内核代码拥有自旋锁,在相关CPU上的抢占就会被禁止。

    适用于自旋锁的核心规则:
    1)任何拥有自旋锁的代码都必须使原子的,除服务中断外(某些情况下也不能放弃CPU,如中断服务也要获得自旋锁。为了避免这种锁陷阱,需要在拥有自旋锁时禁止中断),不能放弃CPU(如休眠,休眠可发生在许多无法预期的地方)。否则CPU将有可能永远自旋下去(死机)。
    2)拥有自旋锁的时间越短越好。


    需要强调的是,自旋锁设计用于多处理器的同步机制,对于单处理器(对于单处理器并且不可抢占的内核来说,自旋锁什么也不作),内核在编译时不会引入自旋锁机制,对于可抢占的内核,它仅仅被用于设置内核的抢占机制是否开启的一个开关,也就是说加锁和解锁实际变成了禁止或开启内核抢占功能。如果内核不支持抢占,那么自旋锁根本就不会编译到内核中。
    内核中使用spinlock_t类型来表示自旋锁,它定义在<linux/spinlock_types.h>

    [html] view plaincopy
    1. typedef struct {  
    2.     raw_spinlock_t raw_lock;  
    3. #if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)  
    4.     unsigned int break_lock;  
    5. #endif  
    6. } spinlock_t;  
    7.    

    对于不支持SMP的内核来说,struct raw_spinlock_t什么也没有,是一个空结构。对于支持多处理器的内核来说,struct raw_spinlock_t定义为

    [html] view plaincopy
    1. typedef struct {  
    2.     unsigned int slock;  
    3. } raw_spinlock_t;<span style="color:#2a2a2a;"><span style="font-family:Times New Roman;"> </span></span>  

    slock表示了自旋锁的状态,“1”表示自旋锁处于解锁状态(UNLOCK),“0”表示自旋锁处于上锁状态(LOCKED)。
    break_lock
    表示当前是否由进程在等待自旋锁,显然,它只有在支持抢占的SMP内核上才起作用。
        
    自旋锁的实现是一个复杂的过程,说它复杂不是因为需要多少代码或逻辑来实现它,其实它的实现代码很少。自旋锁的实现跟体系结构关系密切,核心代码基本也是由汇编语言写成,与体协结构相关的核心代码都放在相关的<asm/>目录下,比如<asm/spinlock.h>。对于我们驱动程序开发人员来说,我们没有必要了解这么spinlock的内部细节,如果你对它感兴趣,请参考阅读Linux内核源代码。对于我们驱动的spinlock接口,我们只需包括<linux/spinlock.h>头文件。在我们详细的介绍spinlockAPI之前,我们先来看看自旋锁的一个基本使用格式:

     使用示例:

    [html] view plaincopy
    1. #include <linux/spinlock.h>  
    2. spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;  
    3. spin_lock(&mr_lock);  
    4. /*  
    5. 临界区  
    6. */  
    7. spin_unlock(&mr_lock);  

    从使用上来说,spinlockAPI还很简单的,一般我们会用的的API如下表,其实它们都是定义在<linux/spinlock.h>中的宏接口,真正的实现在<asm/spinlock.h>

    1. #include <linux/spinlock.h>
      SPIN_LOCK_UNLOCKED
      DEFINE_SPINLOCK
      spin_lock_init( spinlock_t *)
      spin_lock(spinlock_t *)
      spin_unlock(spinlock_t *)
      spin_lock_irq(spinlock_t *)
      spin_unlock_irq(spinlock_t *)
      spin_lock_irqsace(spinlock_t *
      unsigned long flags)
      spin_unlock_irqsace(spinlock_t *, unsigned long flags)
      spin_trylock(spinlock_t *)
      spin_is_locked(spinlock_t *)
       

    初始化

    spinlock有两种初始化形式,一种是静态初始化,一种是动态初始化。对于静态的spinlock对象,我们用 SPIN_LOCK_UNLOCKED来初始化,它是一个宏。当然,我们也可以把声明spinlock和初始化它放在一起做,这就是 DEFINE_SPINLOCK宏的工作,因此,下面的两行代码是等价的。

    [html] view plaincopy
    1. DEFINE_SPINLOCK (lock);  
    2. spinlock_t lock = SPIN_LOCK_UNLOCKED;  

    spin_lock_init函数一般用来初始化动态创建的spinlock_t对象,它的参数是一个指向spinlock_t对象的指针。当然,它也可以初始化一个静态的没有初始化的spinlock_t对象。

    [html] view plaincopy
    1. spinlock_t *lock  
    2. ......  
    3. spin_lock_init(lock);  

    获取锁

    内核提供了多个函数用于获取一个自旋锁。

    [html] view plaincopy
    1. spin_try_lock()        试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.  
    2. spin_is_locked()          //方法和spin_try_lock()是一样的功效,该方法只做判断,并不生效.<span style="color:#2a2a2a;">如果是则返回非<span style="font-family:Times New Roman;">0</span>,否则,返回<span style="font-family:Times New Roman;">0</span></span>  
    3. spin_lock();              //获取指定的自旋锁  
    4. spin_lock_irq();          //禁止本地中断获取指定的锁  
    5. spin_lock_irqsave();      //保存本地中断的状态,禁止本地中断,并获取指定的锁  


    自旋锁是可以使用在中断处理程序中的,这时需要使用具有关闭本地中断功能的函数,我们推荐使用 spin_lock_irqsave,因为它会保存加锁前的中断标志,这样就会正确恢复解锁时的中断标志。如果spin_lock_irq在加锁时中断是关闭的,那么在解锁时就会错误的开启中断。

    释放锁

    同获取锁相对应,内核提供了三个相对的函数来释放自旋锁。

    [html] view plaincopy
    1. <span style="color:#000000;">spin_unlock:释放指定的自旋锁。  
    2. spin_unlock_irq:释放自旋锁并激活本地中断。  
    3. spin_unlock_irqsave:释放自旋锁,并恢复保存的本地中断状态。</span>  


    四、读写自旋锁

    如果临界区保护的数据是可读可写的,那么只要没有写操作,对于读是可以支持并发操作的。对于这种只要求写操作是互斥的需求,如果还是使用自旋锁显然是无法满足这个要求(对于读操作实在是太浪费了)。为此内核提供了另一种锁-读写自旋锁,读自旋锁也叫共享自旋锁,写自旋锁也叫排他自旋锁。

    读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元,当然,读和写也不能同时进行。
        
    读写自旋锁的使用也普通自旋锁的使用很类似,首先要初始化读写自旋锁对象:

    [html] view plaincopy
    1. //静态初始化  
    2. rwlock_t rwlock = RW_LOCK_UNLOCKED;  
    3. //动态初始化  
    4. rwlock_t *rwlock;  
    5. ...  
    6. rw_lock_init(rwlock);  


     在读操作代码里对共享数据获取读自旋锁:

    [html] view plaincopy
    1. read_lock(&rwlock);  
    2. ...  
    3. read_unlock(&rwlock);  


     在写操作代码里为共享数据获取写自旋锁:

    [html] view plaincopy
    1. write_lock(&rwlock);  
    2. ...  
    3. write_unlock(&rwlock);  


     需要注意的是,如果有大量的写操作,会使写操作自旋在写自旋锁上而处于写饥饿状态(等待读自旋锁的全部释放),因为读自旋锁会自由的获取读自旋锁。

    读写自旋锁的函数类似于普通自旋锁,这里就不一一介绍了,我们把它列在下面的表中。

    [html] view plaincopy
    1. RW_LOCK_UNLOCKED  
    2. rw_lock_init(rwlock_t *)  
    3. read_lock(rwlock_t *)  
    4. read_unlock(rwlock_t *)  
    5. read_lock_irq(rwlock_t *)  
    6. read_unlock_irq(rwlock_t *)  
    7. read_lock_irqsave(rwlock_t *, unsigned long)  
    8. read_unlock_irqsave(rwlock_t *, unsigned long)  
    9. write_lock(rwlock_t *)  
    10. write_unlock(rwlock_t *)  
    11. write_lock_irq(rwlock_t *)  
    12. write_unlock_irq(rwlock_t *)  
    13. write_lock_irqsave(rwlock_t *, unsigned long)  
    14. write_unlock_irqsave(rwlock_t *, unsigned long)  
    15. rw_is_locked(rwlock_t *)  

    五、顺序琐

    顺序琐(seqlock)是对读写锁的一种优化,若使用顺序琐,读执行单元绝不会被写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序琐保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。但是,写执行单元与写执行单元之间仍然是互斥的,即如果有写执行单元在进行写操作,其它写执行单元必须自旋在哪里,直到写执行单元释放了顺序琐。

    如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据,以便确保得到的数据是完整的,这种锁在读写同时进行的概率比较小时,性能是非常好的,而且它允许读写同时进行,因而更大的提高了并发性,

    注意,顺序琐由一个限制,就是它必须被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致Oops

    六、信号量

    Linux中的信号量是一种睡眠锁,如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠,这时处理器能重获自由,从而去执行其它代码,当持有信号量的进程将信号量释放后,处于等待队列中的哪个任务被唤醒,并获得该信号量。

    信号量,或旗标,就是我们在操作系统里学习的经典的P/V原语操作。
    P
    :如果信号量值大于0,则递减信号量的值,程序继续执行,否则,睡眠等待信号量大于0
    V
    :递增信号量的值,如果递增的信号量的值大于0,则唤醒等待的进程。


      
      信号量的值确定了同时可以有多少个进程可以同时进入临界区,如果信号量的初始值始1,这信号量就是互斥信号量(MUTEX)。对于大于1的非0值信号量,也可称为计数信号量(counting semaphore)。对于一般的驱动程序使用的信号量都是互斥信号量。

    类似于自旋锁,信号量的实现也与体系结构密切相关,具体的实现定义在<asm/semaphore.h>头文件中,对于x86_32系统来说,它的定义如下:

    [html] view plaincopy
    1. struct semaphore {  
    2.    <span style="color:#ff9900;">atomic_t</span> count;  
    3.    int sleepers;  
    4.    wait_queue_head_t wait;  
    5. };  


     信号量的初始值countatomic_t类型的,这是一个原子操作类型,它也是一个内核同步技术,可见信号量是基于原子操作的。我们会在后面原子操作部分对原子操作做详细介绍。

    信号量的使用类似于自旋锁,包括创建、获取和释放。我们还是来先展示信号量的基本使用形式:

    [html] view plaincopy
    1. static DECLARE_MUTEX(my_sem);  
    2. ......  
    3. if (down_interruptible(&my_sem))  
    4. {  
    5.     return -ERESTARTSYS;  
    6. }  
    7. ......  
    8. up(&my_sem);  


     Linux内核中的信号量函数接口如下:

    static DECLARE_SEMAPHORE_GENERIC(name, count);
    static DECLARE_MUTEX(name);
    seam_init(struct semaphore *, int);
    init_MUTEX(struct semaphore *);
    init_MUTEX_LOCKED(struct semaphore *)
    down_interruptible(struct semaphore *);
    down(struct semaphore *)
    down_trylock(struct semaphore *)
    up(struct semaphore *)

    初始化信号量

    信号量的初始化包括静态初始化和动态初始化。静态初始化用于静态的声明并初始化信号量。

    static DECLARE_SEMAPHORE_GENERIC(name, count);
    static DECLARE_MUTEX(name);

     

    对于动态声明或创建的信号量,可以使用如下函数进行初始化:

    seam_init(sem, count);
    init_MUTEX(sem);
    init_MUTEX_LOCKED(struct semaphore *)

     

    显然,带有MUTEX的函数始初始化互斥信号量。LOCKED则初始化信号量为锁状态。

    使用信号量

    [html] view plaincopy
    1. down_interruptible(struct semaphore *);          
    2.                                    <span style="color:#3366ff;">使进程进入可中断的睡眠状态。关于进程状态的详细细节,我们在内核的进程管理里在做详细介绍。  
    3. </span>down(struct semaphore *)           尝试获取指定的信号量,如果信号量已经被使用了,则进程进入不可中断的睡眠状态。  
    4. down_trylock(struct semaphore *)   尝试获取信号量,如果获取成功则返回0,失败则会立即返回非0。  
    5. up(struct semaphore *)             释放信号量,如果信号量上的睡眠队列不为空,则唤醒其中一个等待进程。  



    七、读写信号量

    类似于自旋锁,信号量也有读写信号量。读写信号量API定义在<linux/rwsem.h>头文件中,它的定义其实也是体系结构相关的,因此具体实现定义在<asm/rwsem.h>头文件中,以下是x86的例子:

    struct rw_semaphore {
        signed long       count;
        spinlock_t       wait_lock;
        struct list_head    wait_list;
    };

     

    首先要说明的是所有的读写信号量都是互斥信号量。读锁是共享锁,就是同时允许多个读进程持有该信号量,但写锁是独占锁,同时只能有一个写锁持有该互斥信号量。显然,写锁是排他的,包括排斥读锁。由于写锁是共享锁,它允许多个读进程持有该锁,只要没有进程持有写锁,它就始终会成功持有该锁,因此这会造成写进程写饥饿状态。

    在使用读写信号量前先要初始化,就像你所想到的,它在使用上几乎与读写自旋锁一致。先来看看读写信号量的创建和初始化:

    //静态初始化
    static DECLARE_RWSEM(rwsem_name);

    // 
    动态初始化
    static struct rw_semaphore rw_sem

    init_rwsem(&rw_sem);

     

    读进程获取信号量保护临界区数据:

    down_read(&rw_sem);
    ...
    up_read(&rw_sem);

     

    写进程获取信号量保护临界区数据:

    down_write(&rw_sem);
    ...
    up_write(&rw_sem);

     

    更多的读写信号量API请参考下表:

    #include <linux/rwsem.h>

    DECLARE_RWSET(name);
    init_rwsem(struct
      rw_semaphore *);
    void down_read(struct rw_semaphore *sem);
    void down_write(struct rw_semaphore *sem);
    void up_read(struct rw_semaphore *sem);
    int down_read_trylock(struct rw_semaphore *sem);
    int down_write_trylock(struct rw_semaphore *sem);
    void downgrade_write(struct rw_semaphore *sem);
    void up_write(struct rw_semaphore *sem);

     

    同自旋锁一样,down_read_trylockdown_write_trylock会尝试着获取信号量,如果获取成功则返回1,否则返回0。奇怪为什么返回值与信号量的对应函数相反,使用是一定要小心这点。

     

    九、自旋锁和信号量区别

    在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

      自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。

      自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"

      但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"

      鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

    区别总结如下:

    1、由于争用信号量的进程在等待锁重新变为可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。

    2、相反,锁被短时间持有时,使用信号量就不太适宜了,因为睡眠引起的耗时可能比锁被占用的全部时间还要长。

    3、由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下文中(使用自旋锁)是不能进行调度的。

    4、你可以在持有信号量时去睡眠(当然你也可能并不需要睡眠),因为当其它进程试图获得同一信号量时不会因此而死锁,(因为该进程也只是去睡眠而已,而你最终会继续执行的)。

    5、在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。

    6、信号量锁保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区,因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一进程企图获取本自旋锁,死锁就会发生。

    7、信号量不同于自旋锁,它不会禁止内核抢占(自旋锁被持有时,内核不能被抢占),所以持有信号量的代码可以被抢占,这意味着信号量不会对调度的等待时间带来负面影响。

    除了以上介绍的同步机制方法以外,还有BKL(大内核锁),Seq锁等。

    BKL是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过度到细粒度加锁机制。

    Seq锁用于读写共享数据,实现这样锁只要依靠一个序列计数器。
    展开全文
  • 展开全部Java的线程机制 摘 要: 多线程机制是Java的重要技术,阐述了636f70793231313335323631343130323136353331333339656535线程和进程的差别;Java中线程4个状态之间的转换;并结合例子说明了两种创建线程的方法...

    展开全部

    Java的线程机制 摘 要: 多线程机制是Java的重要技术,阐述了636f70793231313335323631343130323136353331333339656535线程和进程的差别;Java中线程4个状态之间的转换;并结合例子说明了两种创建线程的方法。 线程是指程序中能顺序执行的一个序列。一个线程只有一个入口点? 但可能有几个出口点? 不过,每个时刻的执行点总是只有一个。线程是不能独立运行的程序,而只是某个整体程序内部的一个顺序执行流。 多线程是Java的一个重要特点。如果一个程序是单线程的,那么,任何时刻都只有一个执行点。这种单线程执行方法使系统运行效率低,而且,由于必须依靠中断来处理输入/输出。所以,当出现频繁输入/输出或者有优先级较低的中断请求时,实时性就变得很差。多线程系统可以避免这个缺点。所谓多线程,就是通过系统的调度使几个具有不同功能的程序流即线程同时并行地运行。 在单处理器计算机系统中,实际上是不可能使多个线程真正并行运行的,而要通过系统用极短的时间、极快的速度对多个线程进行切换,宏观上形成多个线程并发执行的效果。 1 线程和进程机制上的差别 线程和进程很相象,它们都是程序的一个顺序执行序列,但两者又有区别。进程是一个实体,每个进程有自己独立的状态,并有自己的专用数据段,创建进程时, 必须建立和复制其专用数据段,线程则互相共享数据段。同一个程序中的所有线程只有一个数据段, 所以, 创建线程时不必重新建立和复制数据段。由于数据段建立和复制这方面的差异,使线程的建立和线程间的切换速度大大优于进程,另一方面,线程又具备进程的大多数优点。 假设银行系统办理存款和取款手续,将帐本看成数据段。如果按进程这种机制,那么,当储户去存/取款时,银行应先把帐本复制一遍,为储户建立一个独立的帐本再结算。如果按线程机制, 那么,银行里所有的出纳员都用同一个帐本,储户来办存/取款时,也从这个帐本直接结算。用线程机制省去了数据段复制这一步显然是线程独具的特点。 由于多个线程共享一个数据段,所以,也出现了数据访问过程的互斥和同步问题,这使系统管理功能变得相对复杂。 总的来说,一个多线程系统在提高系统的输入/输出速度、有效利用系统资源、改善计算机通信功能以及发挥多处理器硬件功能方面显示了很大优势。因此,一些最新的操作系统如Windows95、Windows98、Windows NT等都提供了对多线程的支持。但是,在多线程操作系统下设计多线程的程序仍然是一个比较复杂和困难的工作。由于需要解决对数据段的共享,所以,原则上应该从程序设计角度采用加锁和释放措施,稍有不慎,便会使系统产生管理上的混乱。 而Java从语言一级提供对多线程的支持,这样,可由语言和运行系统联合提供对共享数据段的管理功能和同步机制,使得多线程并行程序设计相对比较容易。 2 Java线程的生命周期 每个线程都是和生命周期相联系的,一个生命周期含有多个状态,这些状态间可以互相转化。 Java的线程的生命周期可以分为4个状态;创建(new)状态;可运行(runnable)状态;不执行(notrunnable)状态;消亡(dead)状态。 创建状态是指创建一个线程对应的对象的过程,Java系统中,些对象都是从Java.lang包内一个称为Thread的类用关键字new创建的。刚创建的线程不能执行,必须向系统进行注册、分配必要的资源后才能进入可运行状态,这个步骤是由start操作完成的,而处于可运行状态的线程也未必一定处于运行中,它有可能由于外部的I/O请求而处于不运行状态。进入消亡状态后,此线程就不再存在了。 一个线程创建之后,总是处于其生命周期的4个状态之一中,线程的状态表明此线程当前正在进行的活动,而线程的状态是可以通过程序来进行控制的,就是说,可以对线程进行操作来改变状态。 这些操作包括启动(start)、终止(stop)、睡眠(sleep)、挂起(suspend)、恢复(resume)、等待(wait)和通知(notify)。每一个操作都对应了一个方法? 这些方法是由软件包Java.lang提供的。通过各种操作,线程的4个状态之间可按图1所示进行转换。 2.1 创建(new)状态 如果创建了一个线程而没有启动它,那么,此线程就处于创建状态。比如,下述语句执行以后,使系统有了一个处于创建状态的线程myThread:? Thread myThread=new MyThreadClass(); 其中,MyThreadClass()是Thread的子类,而Thread是由Java系统的Java.lang软件包提供的。处于创建状态的线程还没有获得应有的资源,所以,这是一个空的线程,线程只有通过启动后,系统才会为它分配资源。对处于创建状态的线程可以进行两种操作: 一是启动(start)操作,使其进入可运行状态;二是终止(stop)操作,使其进入消亡状态。如果进入到消亡状态,那么,此后这个线程就不能进入其它状态,也就是说,它不复存在了。 start方法是对应启动操作的方法,其具体功能是为线程分配必要的系统资源,将线程设置为可运行状态,从而可以使系统调度这个线程。 2.2 可运行(runnable)状态 如果对一个处于创建状态的线程进行启动操作,则此线程便进入可运行状态。比如,用下列语句? myThread.start();? ?? 则使线程myThread进入可运行状态。上述语句实质上是调用了线程体即run()方法,注意,run()方法包含在myThread线程中,也就是先由java.lang包的Thread类将run()方法传递给子类MyThreadClass(),再通过创建线程由子类MyThreadClass,传递给线程myThread。 线程处于可运行状态只说明它具备了运行条件,但可运行状态并不一定是运行状态,因为在单处理器系统中运行多线程程序,实际上在一个时间点只有一个线程在运行,而系统中往往有多个线程同时处于可运行状态,系统通过快速切换和调度使所有可运行线程共享处理器,造成宏观上的多线程并发运行。可见,一个线程是否处于运行状, 除了必须处于可运行状态外,还取决于系统的调度。 在可运行状态可以进行多种操作,最通常的是从run()方法正常退出而使线程结束,进入消亡状态。 此, 还可以有如下操作? 挂起操作,通过调用suspend方法来实现; 睡眠操作,通过调用sleep方法来实现; 等待操作,通过调用wait方法来实现; 退让操作,通过调用yield方法来实现; 终止操作,通过调用stop方法来实现。 前面三种操作都会使一个处于可运行状态的线程进入不可运行状态。比如,仍以myThread线程为例,当其处于可运行状态后,再用如下语句? myThread.sleep (5000); 则调用sleep方法使myThread线程睡眠5s(5000ms)。这5s内,此线程不能被系统调度运行,只有过5s后,myThread线程才会醒来并自动回到可运行状态。 如果一个线程被执行挂起操作而转到不可运行状态,则必须通过调用恢复(resume)操作,才能使这个线程再回到可运行状态。 退让操作是使某个线程把CPU控制权提前转交给同级优先权的其他线程。 对可运行状态的线程也可以通过调用stop方法使其进入消亡状态。 2.3 不可运行(not runnable)状态 不可运行状态都是由可运行状态转变来的。一个处于可运行状态的线程,如果遇到挂起(suspend)操作、睡眠(sleep)操作或者等待(wait)操作,就会进入不可运行状态。 另外,如果一个线程是和I/O操作有关的,那么,在执行I/O指令时,由于外设速度远远低于处理器速度而使线程受到阻, 从而进入不可运行状态,只有外设完成输入/输出之后,才会自动回到可运行状态。线程进入不可运行状态后,还可以再回到可运行状态,通常有三种途径使其恢复到可运行状态。 一是自动恢复。通过睡眠(sleep)操作进入不可运行状态的线程会在过了指定睡眠时间以后自动恢复到可运行状态,由于I/O阻塞而进入不可运行状态的线程在外设完成I/O操作后,自动恢复到可运行状态。 二是用恢复(resume)方法使其恢复。如果一个线程由于挂起(suspend)操作而从可运行状态进入不可运行状态,那么,必须用恢复(resume)操作使其再恢复到可运行状态。 三是用通知(notify或notifyAll)方法使其恢复。如果一个处于可运行状态的线程由于等待(wait)操作而转入不可运行状态,那么,必须通过调用notify方法或notifyAll方法才能使其恢复到可运行状态,采用等待操作往往是由于线程需要等待某个条件变量,当获得此条件变量后,便可由notify或ontifyAll方法使线程恢复到可运行状态。 恢复到可运行状态的每一种途径都是有针对性的,不能交叉。比如,对由于阻塞而进入不可运行状态的线程采用恢复操作将是无效的。 在不可运行状态,也可由终止(stop)操作使其进入消亡状态。 2.4 消亡(dead)状态 一个线程可以由其他任何一个状态通过终止(stop)操作而进入消亡状态。 线程一旦进入消亡状态,那它就不再存在了,所以也不可能再转到其它状态。 通常,在一个应用程序运行时,如果通过其它外部命令终止当前应用程序,那么就会调用(stop)方法终止线程。但是,最正常、最常见的途径是由于线程在可运行状态正常完成自身的任务而″寿终正寝″,从而进入消亡状态,这个完成任务的动作是由run方法实现的。 3 Java线程的两种创建途径 一种途径是通过对Thread的继承来派生一个子类,再由此子类生成一个对象来实现线程的创建,这是比较简单直接的办法。Thread类包含在系统API提供的8个软件包之一Java.lang中,Thread类中包含了很多与线程有关的方, 其中,一个名为run的方法就是用来实现线程行为的。比如:? 1 import java.lang.*? //引用lang包 2 class Mango exteds Thread { 3 public void run() { 4 ...... 5 ?} 6 ?} 上述程序段中,第1行语句引用软件包lang,这样做是为了给编译器一个信息,从而使后面程序中有关lang包中的方法可直接用方法名,而不必带前缀“Java.lang”。第2行语句是从lang包Thread派生一个子类Mango, 而这个子类中提供了run方法的实现,这样,运行时,将由子类Mango 的 run方法置换父类Thread的run方法。 不过这一步还没有创建线, 必须由子类生成一个对象,并且进行启动操作,这样才能得到一个处于可运行状态的线程。生成对象其实就是完成线程的创建,而启动是对已创建的线程进行操作。具体语句如下:? Mango t=new Mango(); ?? t.start(); ?? 上面先用关键字new使线程进入创建状态,又调用start()方法使线程进入可运行状态。注意,start()方法是由Thread继承给子类Mango、然后又在生成对象时由对象t从类Mango得到的。 另一种途径是通过一个类去继承接口runnable来实现线程的创建? 而这个类必须提供runnable接口中定义的方法run()的实现。runnable是Java.lang包中的一个接口,在runnable接口中,只定义了一个抽象方法run()。所以,如用这种途径来创建线程,则应先由一个类连接接口runnable,并且提供run()方法的实现。比如,下面的程序段实现了与接口的连接。 1 public class xyz implements Runnable{ 2 int i; ? 3 public voed run(){ 4 while (true){? ? 5 System.out.println("Hello"+i++); 6 ? } 7 ? } 8 ? } 然后再创建一个线程? runnable r=new xyz(); ?? Thread t=new Thread(r); 这种途径创建线程比第一种途径灵活。当一个类既需要继承一个父类又要由此创建一个线程时,由于Java不支持多重继承,这样,用第一种途径将行不通,因为,按此思路创建线程也是以继承的方法实现的。 于是,就需要一个类既继承Thread类,又继承另一个父类。但用接口方法却能实现这个目标。 4 线程的启动和终止 Thread的start()方法对应于启动操作,它完成两方面的功能:一方面是为线程分配必要的资源,使线程处于可运行状态,另一方面是调用线程的run()方法置换Thread的中run()方法或者置换runnable中的run()方法来运行线程。 使用start()方法的语句很简单,即: ThreadName.start(); 下面的程序段先创建并启动线程myThread, 然后使用sleep()方法让其睡眠20000ms即20s,使其处于不可运行状态,过20s后,线程又自动恢复到可运行状态。 Thread MyThread=new MyThreadClass(); MyThread.start();? ?? try{ ? MyThread.sleep(20000); ?} catch(InterrujptedException e){ } ?

    2Q==

    已赞过

    已踩过<

    你对这个回答的评价是?

    评论

    收起

    展开全文
  •  一、网络的服务质量(QoS) 目前,视频会议系统常用的网络主要E1专线和IP两种。E1专线基于电路交换和时分复用技术,能够提供端到端的独享带宽,因此网络本身具有完善的传输质量保障机制。在绝大多数情况下,...
     在视频会议系统的应用中,影响视音频效果的因素主要集中在三个方面:
    

      1)网络的服务质量;

      2)MCU和终端的性能;

      3)会议室的设计。

      一、网络的服务质量(QoS)

      目前,视频会议系统常用的网络主要有E1专线和IP两种。E1专线基于电路交换和时分复用技术,能够提供端到端的独享带宽,因此网络本身具有完善的传输质量保障机制。在绝大多数情况下,影响E1专线传输效果的主要因素就是传输设备和传输线路的质量。对于这类因素,我们往往可以通过更换传输设备和降低线路误码率进行改善。

      而IP网基于统计复用和分组交换技术,在需要同时传输语音、数据以及视频等多种业务时,其传统的“尽力传递”机制暴露出很多问题,其中最重要的一点就是无法为每一种业务提供端到端的带宽保证,会导致较大的传输延时和抖动。为此,我们必须通过技术手段对IP网进行优化,以减少网络本身对视频会议系统效果的影响。这些技术手段目前已经发展为IP体系中的一个重要分支,就是服务质量(QoS)。

      所谓QoS,是指一个网络通过多种技术为某一特定的网络流量提供更好服务的能力,它的主要目的是实现优先权控制,包括带宽、延时、抖动以及丢包等多个方面。几乎所有的网络都可以利用QoS的优势来获得最佳的效率。

      QoS技术分为三类,包括尽力而为服务、集成服务、差分服务,其中差分服务应用最广泛。在差分服务中,网络根据每一个数据包的QoS标记对数据包进行分类、排队和管理。这些标记可以是IP地址、TCP端口号或IP数据包中的特定字段。

      在实际的网络规划中,就要求网络设备(如路由器)能够借助于复杂的流量管理系统,通过多种技术提供QoS保证机制,根据业务类型划分不同的优先等级,比如语音最优、视频其次、数据最后,然后根据这些优先级别分配网络资源。

      对于视频会议而言,为了保证视频业务的带宽,路由器必须能够在通过的IP数据流中识别出视频业务数据包并对其分类,然后再通过拥塞管理机制提供带宽保证和优先传递服务。这样,在网络发生拥塞时,就可以保证语音和视频业务的传输效果了。目前主流路由器厂商均可提供基于分类、标记与拥塞管理的QoS支持。

      二、MCU和终端的性能

      除了网络应该提供良好的QoS保障机制外,视频会议系统设备本身也应该具有良好的性能才能真正保证会议的效果。这些性能因素包括系统采用的视音频编解码技术、设备的设计结构、设备本身对恶劣网络环境的适应能力以及其他方面。

      1、视音频编解码技术

      视音频编码技术是视频会议系统的关键技术指标,是影响会议效果的重要因素。目前视频会议系统中用到的视频编码技术主要有H.261、H.263、H.264、MPEG-2、MPEG-4等,音频编码技术主要有G.711、G.722、G.728、G.729、MP3等。

      其中,H.264和MPEG-4这两种视频编码技术能够在低带宽下实现高清晰的动态图像效果,而且编码延时小,作为新一代视频编解码标准,其优势非常明显。

      而在音频编码方面,MP3是一种高效的声音压缩算法,其频响范围在20Hz到20KHz之间,采样频率达到44.1KHz,而且支持双声道编码,因此正在获得越来越广泛的应用。

      2、设备的设计结构

      早期有很多视频会议系统中的MCU和终端均采用PC作为硬件结构,操作系统则基于Windows。这类设备在编解码性能、包转发效率以及稳定性、安全性等方面均存在很大的局限性,导致会议视音频质量不高、延时较大。

      作为专业的会议室型应用,绝大多数视频会议系统现在都选择基于嵌入式设计结构的MCU和终端设备。这主要是因为嵌入式系统指令精简、实时性高,结合专用的编解码DSP,可实现高品质、低延时的视音频信号处理,而且稳定性、安全性也高。

      3、设备对恶劣网络环境的适应能力

      网络的QoS可以在一定程度上保证视频会议的传输效果,但其作用是很有限的,尤其是在一些较为恶劣的网络环境下。视频会议系统设备本身对恶劣网络环境的适应能力也将对会议效果产生较大的影响。这些适应能力包括IP优先权设置、IP包排序、IP包重复控制、IP包抖动控制、丢包重传以及速率自动调整等。

      1)IP优先权(IP Precedence)

      在网络规划差分服务方式的QoS技术时,可通过多种匹配手段对进入数据网的业务包进行分类,包括IP地址、IP 优先权(IP Precedence)等。

      其中,利用IP包中的IP优先权部分可以对音频、视频和RTCP(Multicast)数据流进行优先级划分。当网络采用IP Precedence进行流量匹配时,可通过视频设备发出的修改过IP Precedence字段信息的视音频包进行入队列处理,以保证视频会议码流的优先传送。

      2)IP包排序

      通常,网络的尽力传递机制无法保证其转发的数据包的正确次序。对于H.323视频会议系统,如果视频设备按次序接收IP包,将带来错序问题,数据包的丢失或延迟将导致视频图像的冻结或声音的中断或抖动。

      可通过视频设备支持IP包排序功能解决该问题,当IP包到达时,视频设备将对其次序进行验证,无序的包被退回,以维护发送给终端用户的音频和视频流的连续性。

      3)IP包重复控制

      一个IP包经过承载网时可能会产生多个重复的副本,或为了适应恶劣网络环境系统可能采用重传机制时也会产生多个重复的副本,这样将引起视频图像的冻结或声音中断。支持IP包重复控制的视频设备可通过该功能来纠正该错误,以维护发送给终端用户的音频和视频流的连续性。

      4)抖动控制

      当音频和视频IP包离开发送端时,按照规则的间隔均匀的排列。在通过网络之后,这一均匀的间隔因不同的延时大小而遭到破坏,从而产生抖动。抖动会导致目标终端上音频和视频流的不连贯性。支持抖动控制的视频设备可通过抖动缓存来实现抖动消除,以维护终端用户接收到的音频和视频流的连贯性。

      5)丢包重传

      当网络拥塞严重时,网络设备(如路由器)会根据缓存大小并配合相关处理机制丢掉一些视频包,视频会议系统中视频包是采用UDP协议进行传输的,而UDP本身没有重传机制,因此会导致接收端出现图像丢帧或马赛克现象。支持丢包重传的视频设备可通过添加丢包检测和重传的机制来保证会议图像的连贯性。

      6)自动速率调整技术

      在一些恶劣的网络环境下,降低会议码率将有助于提高视音频的连贯性和实际效果。如果视频设备支持动态速率调整技术,可以使终端和MCU能通过检测网络上有利和不利的因素来自动适应网络的容量和性能,通过动态调整视频会议的码率,为终端用户提供尽可能好的视频质量。

      视频设备的自适应带宽调整功能主要是通过检测数据包丢失率来实现的。如果终端检测到数据包丢失率超过了指定的阀值,它将自动降低视频会议码率,同时通知其它参会终端做相同的动作,从而提供一个具有最优视音频效果的会议码率。

      7)唇音同步技术

      视频会议系统中视频信号和音频信号是分别编码、分别传输的,由于IP优先级和视音频包大小等因素的影响,会使视音频的同步包到达顺序不同,引起唇音不同步。

      影响唇音不同步主要有两种因素:网络传输时延和视音频处理时延不同。

      当音频和视频包离开发送端时,音频包与对应的视频包保持同步。但是,在通过承载网时,各种队列算法会对音频资料包和视频资料包进行不同的处理。这将打乱音频资料包与相应的视频资料包的同步关系。最终的结果导致声音与口型失去同步。支持唇音同步的视频设备可通过使用IP包中的RTP时间戳信息来纠正这一问题。利用RTP时间戳,设备能够确定哪一音频包与哪一视频包对应。进一步重新调整相应的视音频包,以保证声音与口型的同步。

      在发送端,处理音频所花费的时间不同于处理视频所花费的时间。影响这一问题的因素包括声速与光速的不同、房间的大小和形状、音频和视频编码的算法的复杂性。为了避免时间差,支持唇音同步的设备可通过在音频流的出发点增加一定的延迟,以获得声音与口型的同步;也可在接收端增加或减少音频延迟,以纠正发送端不恰当的延迟设置。这样就保证远程会场在接收视频会议声音和图像时,实现唇音同步。满足了部分用户的需求,最多花10万元就能实现立体投影。要介绍的这款产品是四维宇宙立体图象发生器VCT-2005。

      这款AP转换器将以个输入的立体3D信号转换成两个被动3D输出信号。这个单独的左眼和右眼信息被输入到两台放映机,如低成本的LCD放映机,然后通过偏振的3D目镜观看,可以得到高质量的3D影像效果。

      通常我们用显示器观看立体图象时,当刷新率低于60HZ时就会感觉到屏幕中的图象在闪烁。这是因为显示器中的图象是由两幅图象交替输出的,这样实际上只能达到显示器输出刷新率的一半。而立体眼镜是与计算机同步的,即使投影机刷新率高也没用,高刷新的图象根本到达不了投影机。这样频繁闪的图象就会给观看者的眼睛造成疲劳。目前,“立体图象发生器”是采用偏振光技术,将VCT或PC机的视频信号输给DLP/LCD投影机,在大屏幕上投射3D影象。通过偏振光角度来使左右眼同时观看两幅不同的图象。图象的输出不受刷新率的限制。左右眼观看到的是两幅同时输出的不间断的图象,这与我们日常观看图象的习惯一样,不会产生闪烁,彻底消除了闪烁造成的疲劳感。可使观众融入在数字环境中,有身临其境的真实感受。配合数据手套、空间跟踪定位器、操纵杆等,可达到人机互动的效果。

      编号三维投影硬件系统单价数量价格/元

      1四维宇宙立体信号发生器 ¥50,000.001 ¥50,000.00

      2DLP投影机(NEC)3000ANSI 1024*768 ¥15,000.002 ¥30,000.00

      3金属投影幕(120") ¥6,000.00 1 ¥6,000.00

      4专业VR镜头 ¥2,700.00 2 ¥5,400.00

      5 偏振光眼镜 ¥150.00 20 ¥3000.00

      6专业立体安装架 ¥2,600.00 1 ¥2,600.00

      7 安装调试费用 ¥3,000.00 - ¥3,000.00

      8图形工作站(可选配)---

      合计¥100,000.00

      这套设备可应用于图形工作站与普通的计算机,将计算机的视频信号转换成两路RGB信号输给两台DLP投影机,产生3D影象,能投射分辨率为1280X1024,刷新频率85HZ的图像,相对于专业的立体影像发生器这套设备实现起来更加简单,仅需要两台普通的投影机以及一个立体信号发生器,为用户节省了开支,并且这套设备可以用作工程虚拟现实演示、3D游戏、立体电影。

    展开全文
  • 程序员的成长之路互联网/程序员/技术/资料共享关注阅读本文大概需要 4 分钟。来自:网络在服务上线后总有些不尽人意的时候,初次使用Redis集群部署Redis主从同步出现切换故障,也是...
  • 面向对象编程:面向接口编程:2、说说java 解析xml有哪些技术?3、抽象类与接口的区别?4、谈谈表单的同步提交与异步提交的区别? 1、谈谈面向对象编程与面向接口编程的区别? 面向对象编程: 首先java就是面向对象...
  • CMDB具体功能有哪些?云计算是近年来比较火爆的技术,人才需求大、薪资待遇好吸引了很多人加入。由于云计算所涵盖的技术点较多且具备一定的专业性,参加专业学习成为人们的一直选择,下面就给大家讲解一下CMDB相关...
  • 我认为远场语音识别技术难点可以分为3个部分,第一个是多通道同步采集硬件研发,第二个是前端麦克风阵列降噪算法,第三个是后端语音识别与前端信号处理算法的匹配。首先多通道同步采集硬件是研究前端降噪算法的前提...
  • 某天,小五看到小丽愁眉苦脸的,于是问了她什么心事~公司的社区网站访问越来越慢了,特别是搜索功能,这该怎么优化呀?你们都用了啥技术搭建的呀?springboot+mybatis,数据库mysql,还用了redis做缓存。搜索不会...
  • 某天,小五看到小丽愁眉苦脸的,于是问了她什么心事~公司的社区网站访问越来越慢了,特别是搜索功能,这该怎么优化呀?你们都用了啥技术搭建的呀?springboot+mybatis,数据库mysql,还用了redis做缓存。搜索不会...
  • 3、池技术,什么对象池,连接池,线程池……Java反射技术,写框架必备的技术,遇到严重的性能问题,替代方案java字节码技术; 4、nio,没什么好说的,值得注意的是"直接内存"的特点,使用场景;java...
  • 文件同步工具rsync是运维工作中会遇到的命令,那么rsync命令常见模式有哪些?对rsync命令有什么应用技巧吗?rsync:文件同步工具rsync是一款开源的、快速的、多功能的、可实现全量及增量的本地或远程数据镜像同...
  • 点击上方蓝色“冰河技术”,关注并选择“设为星标”持之以恒,贵在坚持,每天进步一点点!作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列...
  • 什么是线程同步通信呢?其实简单来说就是线程间的等待与唤醒;下面我们来了解一下:1.简单多线程通信(现在A、B线程,让线程A先执行10次循环,随后让线程B执行20次,之后反复100次;该如何实现呢?需要注意哪些...
  • 2,常用的锁有哪些? 3,相关链接 什么是锁(临界区) 临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。 常用的锁有哪些? 互斥锁:是一种用于多线程编程中,防止两条线程同时对同一公共...
  • 随着云计算技术的日益成熟,人们可以在日常生活中切实体会到云计算带来的便利。 1.云存储,在国外,云存储服务已经得到市场认可,拥有庞大的用户群和成熟的商业模式。国内在这方面的服务刚刚起步,像“微盘”等专用...
  • 蜂窝网络的同步协议要求终端必须定期联网,具有较短的下行延迟。LoRaWAN采用异步的ALOHA协议,需要定期的唤醒终端,终端可根据具体应用场景需求进行或长或短的休眠。    蜂窝网络的设计理念是为了提高频谱的利用...
  • BIM应用 到工程项目中,以期望达到工程良好的可视性、工程资料与信息的连续与同步以及工程成本的降低等...BIM技术在桥梁运营管养阶段的应用有哪些?一、全景观察BIM技术可以实现工程可视化,即在桥梁工程施工完成之...
  • 2013阿里技术嘉年华:阿里数据同步前世今生  阿里巴巴集团董事局主席马云的话说:“我们不是想高调,我们只想更开放、更透明、更承担责任、更分享和更互动...它的前世今生有哪些趣闻轶事? 对其内容详细阅读
  • 随着分布式技术的普及和海量数据的增长,io的能力越来越重要,java提供的io模块提供了足够的扩展性来适应。我是李福春,我在准备面试,今天的问题是:java中的io哪几种?java中的io分3类:1,BIO ,即同步阻塞IO,...
  • Linux基础优化与安全有哪些重点内容?Linux运维学习是很多人进入it行业的不二选择。互联网技术的迅速发展促使Linux越来越受欢迎。众多行业对Linux越来越受青睐。入门学习Linux技术,是很多人正在开始做的事。那么在...
  • 南京车床的加工范围有哪些近年来,机床制造基础和共性技术研究不断加强,产品开发与技术研究同步推进。机床产品的可靠性设计与性能试验技术、多轴联动加工技术等多项关键技术的成熟度有了很大提升。数字化设计技术...
  • 一直在想有哪些技术能实现一次写博,多站同步。最近网上搜了下,还真有这方面的资料,那就是用Metaweblog的API接口,这种特别像foxmail一样能把多个邮箱都集中在一起管理来收发邮件,Metaweblog能一次把写的博客同步...
  • 前言 跳槽容易,但想拿大厂的offer可不那么容易。...这个就多了,不同公司的技术要求也不一样,但是相同的点在于,大公司对于技术的要求都不会很表面,必然会在一定广度的基础上要求一定的深度。 目录 1.如何对
  • 学的知识点非常的多,那么HTML5有哪些优势呢?  1、跨平台。在多屏年代,开发者的痛苦指数非常高,人人都期盼HTML5能扮演救星。多套代码、不同技术工种、业务逻辑同步,这是折磨人的过程。跨平台技术在早期大多因为...
  • 这就是今天要讲的内容,读完这篇文章你应该要知道常用的集合库有哪些具体容器类,多线程的情况下应该用哪种集合以及它们的主要区别。Java 集合框架Java 集合工具包在 Java.util 包下,它包含了常用的数据结...
  • 距离2020 年 9 月 18 日发布 vue3.0 正式版本已经有一段时间了,作为技术人员,随时保持技术同步是很重要的事情。本文带领大家看一看3.0对比2.x到底有哪些改变。 一、建立项目 vue3.0 有两种建立脚手架的方式 脚手架...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 642
精华内容 256
关键字:

同步技术有哪些