精华内容
下载资源
问答
  • 但涉及到线程安全时,一个好的设计是最好的保护。避免共享资源,并尽量减少 线程间的相互作用,这样可以让它们减少互相的干扰。但是一个完全无干扰的设计是 不可能的。在线程必须交互的情况下,你需要使用同步工具,来...

    应用程序里面多个线程的存在引发了多个执行线程安全访问资源的潜在问题。两 个线程同时修改同一资源有可能以意想不到的方式互相干扰。
    但涉及到线程安全时,一个好的设计是最好的保护。避免共享资源,并尽量减少 线程间的相互作用,这样可以让它们减少互相的干扰。但是一个完全无干扰的设计是 不可能的。在线程必须交互的情况下,你需要使用同步工具,来确保当它们交互的时 候是安全的。

    • 同步工具
      • 1 原子操作
        原子操作是同步的一个简单的形式,它处理简单的数据类型。原子操作的优势是 它们不妨碍竞争的线程。对于简单的操作,比如递增一个计数器,原子操作比使用锁 具有更高的性能优势。
      • 2 内存屏障和 Volatile 变量
        为了达到最佳性能,编译器通常会对汇编基本的指令进行重新排序来尽可能保持 处理器的指令流水线。作为优化的一部分,编译器有可能对访问主内存的指令,如果 它认为这有可能产生不正确的数据时,将会对指令进行重新排序。不幸的是,靠编译 器检测到所有可能内存依赖的操作几乎总是不太可能的。如果看似独立的变量实际上 是相互影响,那么编译器优化有可能把这些变量更新位错误的顺序,导致潜在不不正 确结果。
        内存屏障(memory barrier)是一个使用来确保内存操作按照正确的顺序工作的 非阻塞的同步工具。内存屏障的作用就像一个栅栏,迫使处理器来完成位于障碍前面 的任何加载和存储操作,才允许它执行位于屏障之后的加载和存储操作。内存屏障同 样使用来确保一个线程(但对另外一个线程可见)的内存操作总是按照预定的顺序完 成。如果在这些地方缺少内存屏障有可能让其他线程看到看似不可能的结果(比如, 内存屏障的维基百科条目)。为了使用一个内存屏障,你只要在你代码里面需要的地 方简单的调用 OSMemoryBarrier 函数。
        Volatile 变量适用于独立变量的另一个内存限制类型。编译器优化代码通过加 载这些变量的值进入寄存器。对于本地变量,这通常不会有什么问题。但是如果一个 变量对另外一个线程可见,那么这种优化可能会阻止其他线程发现变量的任何变化。 在变量之前加上关键字 volatile 可以强制编译器每次使用变量的时候都从内存里面 加载。如果一个变量的值随时可能给编译器无法检测的外部源更改,那么你可以把该 变量声明为 volatile 变量。
        因为内存屏障和 volatile 变量降低了编译器可执行的优化,因此你应该谨慎使 用它们,只在有需要的地方时候,以确保正确性。关于更多使用内存屏障的信息,参 阅 OSMemoryBarrier 主页。
      • 3锁
        锁是最常用的同步工具。你可以是使用锁来保护临界区(critical section),这 些代码段在同一个时间只能允许被一个线程访问。比如,一个临界区可能会操作一个 特定的数据结构,或使用了每次只能一个客户端访问的资源。

    锁类型

    • 使用锁
    • 1使用POSIX互斥锁
      POSIX 互斥锁在很多程序里面很容易使用。为了新建一个互斥锁,你声明并初始 化一个 pthread_mutex_t 的结构。为了锁住和解锁一个互斥锁,你可以使用 pthread_mutex_lock 和 pthread_mutex_unlock 函数。
      当你用完一个锁之后,只要简单的调用
    pthread_mutex_destroy 来释放该锁的数据结构。 
    pthread_mutex_t mutex;
    void MyInitFunction()
    { pthread_mutex_init(&mutex, NULL);} 
    void MyLockingFunction(){ 
    
    pthread_mutex_lock(&mutex);// Do work. 
    pthread_mutex_unlock(&mutex);
    } 
    • 2使用NSLock类
      NSLock
    • 3使用@synchronized指令
      @synchronized 指令是在 Objective-C 代码中创建一个互斥锁非常方便的方法。 @synchronized 指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取 同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你 只需要简单的使用 Objective-C 对象作为锁的令牌,如下面例子所示:
      @synchronized

    创建给@synchronized 指令的对象是一个用来区别保护块的唯一标示符。如果你 在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给 anObj 参数,那么每次都将会拥有它的锁,并持续处理,中间不被其他线程阻塞。然 而,如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其他线程将会被阻塞直到第一个线程完成它的临界区。 作为一种预防措施,@synchronized 块隐式的添加一个异常处理例程来保护代码。
    该处理例程会在异常抛出的时候自动的释放互斥锁。这意味着为了使用 @synchronized 指令,你必须在你的代码中启用异常处理。了如果你不想让隐式的异 常处理例程带来额外的开销,你应该考虑使用锁的类。

    • 使用 NSRecursiveLock 对象(递归锁)
      NSRecursiveLock 类定义的锁可以在同一线程多次获得,而不会造成死锁。一个
      递归锁会跟踪它被多少次成功获得了。每次成功的获得该锁都必须平衡调用锁住和解 锁的操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获 得。
      正如它名字所言,这种类型的锁通常被用在一个递归函数里面来防止递归造成阻 塞线程。你可以类似的在非递归的情况下使用他来调用函数,这些函数的语义要求它 们使用锁。以下是一个简单递归函数,它在递归中获取锁。如果你不在该代码里使用 NSRecursiveLock 对象,当函数被再次调用的时候线程将会出现死锁。
    NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
    void MyRecursiveFunction(int value){
        [theLock lock];
        if(value != 0){
        --value;
        MyRecursiveFunction(value);
    }
        [theLock unlock];
    }
    MyRecursiveFunction(5);

    注意:因为一个递归锁不会被释放直到所有锁的调用平衡使用了解锁操作,所以你必须仔细权衡是否决定使用锁对性能的潜在影响。长时间持有一个锁将会导致其他线程阻塞直到递归完成。如果你可以重写你的代码来消除递归或消除使用一个递归锁,你可能会获得更好的性能。

    • 使用 NSConditionLock 对象(条件锁)
      NSConditionLock 对象定义了一个互斥锁,可以使用特定值来锁住和解锁。不要
      把该类型的锁和条件(参见“条件”部分)混淆了。它的行为和条件有点类似,但是 它们的实现非常不同。
      通常,当多线程需要以特定的顺序来执行任务的时候,你可以使用一个 NSConditionLock 对象,比如当一个线程生产数据,而另外一个线程消费数据。生产 者执行时,消费者使用由你程序指定的条件来获取锁(条件本身是一个你定义的整形 值)。当生产者完成时,它会解锁该锁并设置锁的条件为合适的整形值来唤醒消费者 线程,之后消费线程继续处理数据。
      NSConditionLock 的锁住和解锁方法可以任意组合使用。比如,你可以使用 unlockWithCondition:和 lock 消息,或使用 lockWhenCondition:和 unlock 消息。 当然,后面的组合可以解锁一个锁但是可能没有释放任何等待某特定条件值的线程。
      下面的例子显示了生产者-消费者问题如何使用条件锁来处理。想象一个应用程 序包含一个数据的队列。一个生产者线程把数据添加到队列,而消费者线程从队列中 取出数据。生产者不需要等待特定的条件,但是它必须等待锁可用以便它可以安全的 把数据添加到队列。
    id condLock = [[NSConditionLock alloc]initWithCondition:NO_DATA]; 
    while(true) {
    [condLock lock];
    [condLock unlockWithCondition:HAS_DATA];
    }
    • 使用 NSDistributedLock 对象(分布锁)
      NSDistributedLock 类可以被多台主机上的多个应用程序使用来限制对某些共享
      资源的访问,比如一个文件。锁本身是一个高效的互斥锁,它使用文件系统项目来实 现,比如一个文件或目录。对于一个可用的 NSDistributedLock 对象,锁必须由所有 使用它的程序写入。这通常意味着把它放在文件系统,该文件系统可以被所有运行在 计算机上面的应用程序访问。
      不像其他类型的锁,NSDistributedLock 并没有实现 NSLocking 协议,所有它没 有 lock 方法。一个 lock 方法将会阻塞线程的执行,并要求系统以预定的速度轮询锁。 以其在你的代码中实现这种约束,NSDistributedLock 供了一个 tryLock 方法,并 让你决定是否轮询。
      因为它使用文件系统来实现,一个 NSDistributedLock 对象不会被释放除非它的 拥有者显式的释放它。如果你的程序在用户一个分布锁的时候崩溃了,其他客户端简 无法访问该受保护的资源。在这种情况下,你可以使用 breadLock 方法来打破现存的 锁以便你可以获取它。但是通常应该避免打破锁,除非你确定拥有进程已经死亡并不 可能再释放该锁。
      和其他类型的锁一样,当你使用 NSDistributedLock 对象时,你可以通过调用 unlock 方法来释放它。

    • 使用条件
      条件是一个特殊类型的锁,你可以使用它来同步操作必须处理的顺序。它们和互 斥锁有微妙的不同。一个线程等待条件会一直处于阻塞状态直到条件获得其他线程显 式发出的信号。
      由于微妙之处包含在操作系统实现上,条件锁被允许返回伪成功,即使实际上它 们并没有被你的代码告知。为了避免这些伪信号操作的问题,你应该总是在你的条件 锁里面使用一个断言。该断言是一个更好的方法来确定是否安全让你的线程处理。条 件简单的让你的线程保持休眠直到断言被发送信号的线程设置了。
      以下部分介绍了如何在你的代码中使用条件。

    • 1 使用NSCondition类
      NSCondition 类 供了和 POSIX 条件相同的语义,但是它把锁和条件数据结构封 装在一个单一对象里面。结果是一个你可以像互斥锁那样使用的对象,然后等待特定 条件。
      下面显示了一个代码片段,它展示了为等待一个 NSCondition 对象的事件序 列。cocaoCondition 变量包含了一个 NSCondition 对象,而 timeToDoWork 变量是一 个整形,它在其他线程里面发送条件信号时立即递增。

    [cocoaCondition lock];
    while (timeToDoWork <= 0){
    [cocoaCondition wait];
    timeToDoWork--;
    // Do real work here.
    }
    [cocoaCondition unlock];
    展开全文
  • 多线程并不一定是最好的,合适才是最好的多线程主要的优点是价廉物美,启动快、退出快、与其他线程共享核心对象,很容易实现共产主义的伟大梦想。但是其又有不可预期、测试困难的缺点。 使用好多线程,就是要...

    为什么多线程?


    多线程并不一定是最好的,合适才是最好的。

    多线程主要的优点是价廉物美,启动快、退出快、与其他线程共享核心对象,很容易实现共产主义的伟大梦想。但是其又有不可预期、测试困难的缺点。

    使用好多线程,就是要知道何时应该用多线程,何时不该用。如果应该用多线程,如何解决Race Condition问题?如何共享数据?如何提高效率?如何同步线程和数据?总结起来就是:

    • 有始有终,线程的创建和释放都要靠自己
    • 不抛弃不放弃,等一等线程,让它做完自己的工作
    • 文明有序,资源占用无冲突

    但是有时候却不建议使用多线程:

    • 针对于慢速I/O设备,Overlapped I/O更能胜任
    • 程序的健壮性要求很高,值得付出比较多的额外负担,多进程可能更能胜任

    操作线程


    如何创建线程?

    如果要写一个多线程程序,第一步就是创建一个线程,我们可以使用CreateThread API函数,也可以使用_beginthreadex C 函数,其实我大多数时候使用的是Boost库上面的boost::thread对象来创建线程对象。如果有兴趣可以看看Boost库,这里暂且不讨论Boost库thread。

    如果使用上面两个函数,可以去msdn查看。使用上面两种函数创建线程,其线程函数都必须符合以下格式,当然函数名可以更换:

    DWORD WINAPI ThreadFunc(LPVOID n);

    使用CreateThread API函数或者_beginthreadex函数,可以传回两个值用以识别一个新的线程——返回值Handle(句柄)和输出参数lpThread(线程ID)。为了安全防护的缘故,不能根据一个线程的ID获得其handle。

    如何释放线程?

    线程和进程一样,都是核心对象。如何释放线程属于如何释放核心对象的问题。CloseHandle函数在这里起了十分重要的作用。CloseHandle函数的功能是将核心对象的引用计数减1。其不能直接用来释放核心对象,核心对象只有在其引用计数为0的时候会被操作系统自动销毁。

    BOOL CloseHandle(HANDLE hObject);

    如果你不调用该函数,即使线程在创建之后执行完毕,引用计数还是不为0,线程无法被销毁。如果一个进程没有在结束之前对它所打开的核心对象调用CloseHandle,操作系统会自动把那些对象的引用计数减一。虽然操作系统会做这个工作,但是他不知道核心对象实际的意义,也就不可能知道解构顺序是否重要。如果你在循环结构创建了核心对象而没有CloseHandle,好吧!你可能会有几十万个句柄没有关闭,你的系统会因此没有可用句柄,然后各种异常现象就出现了。记住当你完成你的工作,应该调用CloseHandle函数释放核心对象。

    在清理线程产生的核心对象时也要注意这个问题。不要依赖因线程结束而清理所有被这一线程产生的核心对象。面对一个打开的对象,区分其拥有者是进程或是线程是很重要的。这决定了系统何时做清理工作。程序员不能选择有进程或者线程拥有对象,一切都得视对象类型而定。如果被线程打开的核心对象被进程拥有,线程结束是无法清理这些核心对象的。

    线程核心对象与线程

    其实这两个是不同的概念。CreateThread函数返回的句柄其实是指向线程核心对象,而不是直接指向线程本身。在创建一个新的线程时,线程本身会开启线程核心对象,引用计数加1,CreateThread函数返回一个线程核心对象句柄,引用计数再加1,所以线程核心对象一开始引用计数就是2。

    调用CloseHandle函数,该线程核心对象引用计数减一,线程执行完成之后,引用计数再减一为零,该核心对象被自动销毁。

    结束主线程

    首先得了解哪个线程是主线程:程序启动后就执行的线程。主线程有两个特点:

    • 负责GUI主消息循环
    • 主线程结束时,强迫其他所有线程被迫结束,其他线程没有机会执行清理工作

    第二个特点也就意味着,如果你不等待其他线程结束,它们没有机会执行完自己的操作,也没有机会做最后的cleanup操作。我遇到过由于没有等待,而出现程序奔溃的情况。反正很危险。

    结束线程并获取其结束代码

    这个没什么好说的,可以使用ExitThread函数退出线程,返回一个结束代码。GetExitCodeThread函数获取ExitThread函数或者return语句返回的结束代码。不过想通过GetExitCodeThread来等待线程结束是个很糟糕的注意——CPU被浪费了。下一节提及的WaitForSingleObject才是正道。

    终止其他线程

    终止其他线程可以使用TerminateThread()函数,也可以使用全局标记。

    TerminateThread()函数的缺点是:
    1、线程没有机会在结束前清理自己,其堆栈也没有被释放掉,出现内存泄露;
    2、任何与此线程有附着关系的DLLs也没有机会获得线程解除附着通知;
    3、线程进入的Critical Section将永远处于锁定状态(Mutex会返回wait_abandoned状态)。
    4、线程正在处理的数据会处于不稳定状态。

    TerminateThread()唯一可以预期的是:线程handle变成激发状态,并且传回dwExitCode所指定的结束代码。

    设立全局标记的优点:保证目标线程在结束之前安全而一致的状态
    设立全局标记的缺点:线程需要一个polling机制,时时检查标记值。(可以使用一个手动重置的event对象

    等一等线程


    等待一个线程的结束

    使用WaitForSingleObject最显而易见的好处是你终于可以把以下代码精简成一句了。

    for(;;)
    {
      int rc;
      rc = GetExitCodeThread(hThread, &exitCode);
      if(!rc && exitCode != STILL_ACTIVE)
        break;
    }
    → → → → → →
    WaitForSingleObject(hThread, INFINITE);

    其他好处就是:

    • busy loop浪费太多CPU时间
    • 可以设定等待时间

    等待多个线程的结束

    WaitForSingleObject函数不好同时判断多个线程的状态,WaitForMultipleObjects可以同时等待多个线程,可以设定是否等待所有线程执行结束还是只要一个线程执行完立马返回。

    在GUI线程中等待

    在GUI线程中总是要常常回到主消息循环,上述两个wait api函数会阻塞主消息循环。MsgWaitForMultipleObjects函数可以在对象呗激发或者消息到达时被唤醒而返回。

    线程同步


    线程同步主要有Critical Sections、Mutex、Semaphores、Event,除了Critical Section是存在于进程内存空间内,其他都是核心对象

    Critical Sections

    Critical Section用来实现排他性占有,适用范围时单一进程的各个线程之间。

    使用示例:

    CRITICAL_SECTION cs ; // here must be global attributes to related thread
    InitializeCriticalSection (&cs );
    EnterCriticalSection(&cs );
    LeaveCriticalSection(&cs );
    DeleteCriticalSection(&cs );

    Critical Sections注意事项:

    • 一旦线程进入一个Critical Section,再调用LeaveCriticalSection函数之前,就能一直重复的进入该Critical Section。
    • 千万不要在一个Critical section之中调用Sleep()或者任何Wait... API函数。
    • 如果进入Critical section的那个线程结束了或者当掉了,而没有调用LeaveCriticalSection函数,系统就没有办法将该Critical Section清除。

    Critical Section的优点:

    • 相对于Mutex来说,其速度很快。锁住一个未被拥有的mutex要比锁住一个未被拥有的critical section,需要花费几乎100倍时间。(critical section不需要进入操作系统核心)

    Critical Section的缺陷:

    • Critical Section不是核心对象,无法WaitForSingleObject,没有办法解决死锁问题(一个著名的死锁问题:哲学家进餐问题)
    • Critical Section不可跨进程
    • 无法指定等待结束的时间长度
    • 不能够同时有一个Critical section被等待
    • 无法侦测是否已被某个线程放弃

    Mutex

    Mutex可以在不同的线程之间实现排他性战友,甚至即使那些线程属于不同进程。

    使用示例:

    HANDLE hMutex ; // global attributes
    hMutex = CreateMutex (
            NULL, // default event attributes
            false, // default not initially owned
            NULL // unnamed
           );
    DWORD dwWaitResult = WaitForSingleObject (hMutex , INFINITE );
    if (dwWaitResult == WAIT_OBJECT_0 )
    {
            // wait succeed, do what you want
           ...
    }
    ReleaseMutex(hMutex );

    示例解释:
    1、HMutex在创建时为未被拥有未激发状态;
    2、调用Wait...()函数,线程获得hMutex的拥有权,HMutex短暂变成激发状态,然后Wait...()函数返回,此时HMutex的状态是被拥有未激发
    3、ReleaseMutex之后,HMutex的状态变为未被拥有未激发状态

    Mutex注意事项:

    • Mutex的拥有权并非属于哪个产生它的哪个线程,而是那个最后对此mutex进行Wait...()操作并且尚未进行ReleaseMutex()操作的线程。
    • 如果线程拥有一个mutex而在结束前没有调用ReleaseMutex(),mutex不会被摧毁,取而代之,该mutex会被视为“未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0通知。
    • Wait...()函数在Mutex处于未被拥有未被激发状态时返回。
    • 将CreateMutex的第二个参数设为true,可以阻止race condition,否则调用CreateMutex的线程还未拥有Mutex,发生了context switch,就被别的线程拥有了。

    Mutex优点

    • 核心对象,可以调用Wait...() API函数
    • 跨线程、跨进程、跨用户(将CreateMutex的第三个参数前加上"Global//")
    • 可以具名,可以被其他进程开启
    • 只能被拥有它的哪个线程释放

    Mutex缺点

    • 等待代价比较大

    Semaphores

    Semaphore被用来追踪有限的资源。

    和Mutex的对比

    • mutex是semaphore的退化,令semahpore的最大值为1,那就是一个mutex
    • semaphore没有拥有权的概念,也没有wait_abandoned状态,一个线程可以反复调用Wait...()函数以产生锁定,而拥有mutex的线程不论在调用多少次Wait...()函数也不会被阻塞。
    • 在许多系统中都有semaphore的概念,而mutex则不一定。
    • 调用ReleaseSemaphore()的那个线程并不一定是调用Wait...()的那个线程,任何线程都可以在任何时间调用ReleaseSemaphore,解除被任何线程锁定的Semaphore。

    Semaphore优点

    • 核心对象
    • 可以具名,可以被其他进程开启
    • 可以被任何一个线程释放

    Semaphore缺点

    Event

    Event通常用于overlapped I/O,或者用来设计某些自定义的同步对象。

    使用示例:

    HANDLE hEvent ; // global attributes
    hEvent = CreateEvent (
            NULL, // default event attributes
            true, // mannual reset
            false, // nonsignaled
            NULL // unnamed
           );
    
    SetEvent(hEvent);
    PulseEvent(hEvent);
    DWORD dwWaitResult = WaitForSingleObject (hEvent , INFINITE );
    ResetEvent(hEvent);
    if (dwWaitResult == WAIT_OBJECT_0 )
    {
            // wait succeed, do what you want
           ...
            ResetEvent(hEvent );
    }

    示例解释:
    1、CreateEvent默认为非激发状态、手动重置
    2、SetEvent把hEvent设为激发状态
    3、在手动重置情况下(bManualReset=true),PulseEvent把event对象设为激发状态,然而唤醒所有等待中的线程,然后恢复为非激发状态;
    4、在自动重置情况下(bManualReset=false),PulseEvent把event对象设为激发状态,然而唤醒一个等待中的线程,然后恢复为非激发状态;
    5、ResetEvent将hEvent设为未激发状态

    Event注意事项:

    • CreateEvent函数的第二个参数bManualReset若为false,event会在变成激发状态(因而唤醒一个线程)之后,自动重置为非激发状态;
    • CreateEvent函数的第二个参数bManualReset若为true,event会在变成激发状态(因而唤醒一个线程)之后,不会自动重置为非激发状态,必须要手动ResetEvent;

    Event优点:

    • 核心对象
    • 其状态完全由程序来控制,其状态不会因为Wait...()函数的调用而改变。
    • 适用于设计新的同步对象
    • 可以具名,可以被其他进程开启

    Event缺点:

    • 要求苏醒的请求并不会被存储起来,可能会遗失掉。如果一个AutoReset event对象调用SetEvent或PulseEvent,而此时并没有线程在等待,这个event会被遗失。如Wait...()函数还没来得及调用就发生了Context Switch,这个时候SetEvent,这个要求苏醒的请求会被遗失,然后调用Wait...()函数线程卡死。

    替代多线程


    Overlapped I/O

    Win32之中三个基本的I/O函数:CreateFile()、ReadFile()和WriteFile()。

    • 设置CreateFile()函数的dwFlagsAndAttributes参数为FILE_FLAG_OVERLAPPED,那么对文件的每一个操作都将是Overlapped。此时可以同时读写文件的许多部分,没有目前的文件位置的概念,每一次读写都要包含其文件位置。
    • 如果发出许多Overlapped请求,那么执行顺序无法保证。
    • Overlapped I/O不能使用C Runtime Library中的stdio.h函数,只能使用ReadFile()和WriteFile()来执行I/O。

    Overlapped I/O函数使用OVERLAPPED结构来识别每一个目前正在进行的Overlapped操作,同时在程序和操作系统之间提供了一个共享区域,参数可以在该区域双向传递。

    多进程

    如果一个进程死亡,系统中的其他进程还是可以继续执行。多进程程序的健壮性远胜于多线程。因为如果多个线程在同一个进程中运行,那么一个误入歧途的线程就可能把整个进程给毁了。

    另一个使用多重进程的理由是,当一个程序从一个作业平台被移植到另一个作业平台,譬如Unix(不支持线程,但进程的产生与结束的代价并不昂贵),Unix应用程序往往使用多个进程,如果移植成为多线程模式,可能需要大改。

    文献



    欢迎访问我的个人博客click me
    博客原文地址:Win32 MultiThread Study Summary - Let's Thread

    后续博客内容维护都会更新在该地址。

    转载于:https://www.cnblogs.com/cnstudy/p/5384450.html

    展开全文
  • 如标题,谢谢大家啦![color=#FF0000](定尽快结贴)[/color]
  • 多进程多线程的选择

    2019-09-27 06:00:29
    关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...

    多进程多线程的选择

    关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你深受其害。

     

    经常在网络上看到有的XDJM问“多进程好还是多线程好?”、“Linux下用多进程还是多线程?”等等期望一劳永逸的问题,我只能说:没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。

     

    我们按照多个不同的维度,来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。

     

     

     

    对比维度

    多进程

    多线程

    总结

    数据共享、同步

    数据共享复杂,需要用IPC;数据是分开的,同步简单

    因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂

    各有优势

    内存、CPU

    占用内存多,切换复杂,CPU利用率低

    占用内存少,切换简单,CPU利用率高

    线程占优

    创建销毁、切换

    创建销毁、切换复杂,速度慢

    创建销毁、切换简单,速度很快

    线程占优

    编程、调试

    编程简单,调试简单

    编程复杂,调试复杂

    进程占优

    可靠性

    进程间不会互相影响

    一个线程挂掉将导致整个进程挂掉

    进程占优

    分布式

    适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单

    适应于多核分布式

    进程占优

     

     

    1)需要频繁创建销毁的优先用线程

    原因请看上面的对比。

    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

    2)需要进行大量计算的优先使用线程

    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

    这种原则最常见的是图像处理、算法处理。

    3)强相关的处理用线程,弱相关的处理用进程

    什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

    一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

    当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

    4)可能要扩展到多机分布的用进程,多核分布的用线程

    原因请看上面对比。

    5)都满足需求的情况下,用你最熟悉、最拿手的方式

    至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。 

    需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

     

    消耗资源:

    从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

    线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

    通讯方式:

    进程之间传递数据只能是通过通讯的方式,即费时又不方便。线程时间数据大部分共享(线程函数内部不共享),快捷方便。但是数据同步需要锁对于static变量尤其注意

    线程自身优势:

    提高应用程序响应;使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;

    改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

    转载于:https://www.cnblogs.com/findbetterme/p/11437204.html

    展开全文
  • 大家谁能介绍一本JAVA多线程的和网络编程的书 最好是PDF的,语言不限,E文也行,但是要经典的 谢谢大家了
  • 《C++面向对象多线程编程》推荐:毫无疑问,这是我见过的最好的、最全面的多线程方面的图书。如果你把自己看作一位程序员,而不是组件装配员,那你就需要了解多线程方面的知识。只要不是刚刚入门的C++程序员都可以从...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...

    鱼还是熊掌:浅谈多进程多线程的选择

    关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你深受其害。

     

    经常在网络上看到有的XDJM问“多进程好还是多线程好?”、“Linux下用多进程还是多线程?”等等期望一劳永逸的问题,我只能说:没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。

     

    我们按照多个不同的维度,来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。

     

     

    1)需要频繁创建销毁的优先用线程

    原因请看上面的对比。

    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

    2)需要进行大量计算的优先使用线程

    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

    这种原则最常见的是图像处理、算法处理。

    3)强相关的处理用线程,弱相关的处理用进程

    什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

    一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

    当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

    4)可能要扩展到多机分布的用进程,多核分布的用线程

    原因请看上面对比。

    5)都满足需求的情况下,用你最熟悉、最拿手的方式

    至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。 

    需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

     

    消耗资源:

    从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

    线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

    通讯方式:

    进程之间传递数据只能是通过通讯的方式,即费时又不方便。线程时间数据大部分共享(线程函数内部不共享),快捷方便。但是数据同步需要锁对于static变量尤其注意

    线程自身优势:

    提高应用程序响应;使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;

    改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

     

    转载于:https://www.cnblogs.com/wzbk/p/8904951.html

    展开全文
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 查看了很篇关于线程中断博文,和书籍,这篇文章对于来说是最好理解 这样情景您也许并不陌生:您在编写一个测试程序,程序需要暂停一段时间,于是调用 Thread.sleep()。但是编译器或 IDE 报错说没有处理检查...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 多进程和多线程的应用场景

    万次阅读 2016-10-09 19:09:56
    关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 多进程多线程的选择

    2010-10-28 12:47:00
    <br />关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 对于锁的分类,我一直都不成系统的记忆,结果就总是忘记,正好前几天看到了上面的导图,所以就依照上面的导图,大体上将...总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...
  • 多线程与多进程

    2019-03-10 15:30:59
    关于多进程和多线程,教科上最经典一句话是“进程是资源分配最小单位,线程是CPU调度最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似选择问题,那就没有这么简单了,选不好,会让你深受...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 214
精华内容 85
关键字:

多线程最好的书