zoukankan      html  css  js  c++  java
  • pthread 学习系列 case2-- 使用互斥锁

    ref http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html

     1 #include <pthread.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <stdio.h>
     5 int myglobal;
     6  void *thread_function(void *arg) {
     7   int i,j;
     8   for ( i=0; i<20; i++) {
     9     j=myglobal;
    10     j=j+1;
    11     printf(".");
    12     fflush(stdout);
    13     sleep(1);
    14     myglobal=j;
    15   }
    16   return NULL;
    17 }
    18 int main(void) {
    19   pthread_t mythread;
    20   int i;
    21   if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    22     printf("error creating thread.");
    23     abort();
    24   }
    25   for ( i=0; i<20; i++) {
    26     myglobal=myglobal+1;
    27     printf("o");
    28     fflush(stdout);
    29     sleep(1);
    30   }
    31   if ( pthread_join ( mythread, NULL ) ) {
    32     printf("error joining thread.");
    33     abort();
    34   }
    35   printf("
    myglobal equals %d
    ",myglobal);
    36   exit(0);
    37 }

    打印输出:

    $ ./thread2
    ..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
    myglobal equals 21

    非常意外吧!因为 myglobal 从零开始,主线程和新线程各自对其进行了 20 次加一, 程序结束时 myglobal 值应当等于 40。由于 myglobal 输出结果为 21,这其中肯定有问题。但是究竟是什么呢?

    放弃吗?好,让我来解释是怎么一回事。首先查看函数 thread_function()。注意如何将 myglobal 复制到局部变量 "j" 了吗? 接着将 j 加一, 再睡眠一秒,然后到这时才将新的 j 值复制到 myglobal?这就是关键所在。设想一下,如果主线程就在新线程将 myglobal 值复制给 j 立即将 myglobal 加一,会发生什么?当 thread_function() 将 j 的值写回 myglobal 时,就覆盖了主线程所做的修改。

    当编写线程程序时,应避免产生这种无用的副作用,否则只会浪费时间(当然,除了编写关于 POSIX 线程的文章时有用)。那么,如何才能排除这种问题呢?

    由于是将 myglobal 复制给 j 并且等了一秒之后才写回时产生问题,可以尝试避免使用临时局部变量并直接将 myglobal 加一。虽然这种解决方案对这个特定例子适用,但它还是不正确。如果我们对 myglobal 进行相对复杂的数学运算,而不是简单的加一,这种方法就会失效。但是为什么呢?

    要理解这个问题,必须记住线程是并发运行的。即使在单处理器系统上运行(内核利用时间分片模拟多任务)也是可以的,从程序员的角度,想像两个线程是同时执行的。thread2.c 出现问题是因为 thread_function() 依赖以下论据:在 myglobal 加一之前的大约一秒钟期间不会修改 myglobal。需要有些途径让一个线程在对 myglobal 做更改时通知其它线程“不要靠近”。

    个人分析:

    线程 thread_function 先将global的值保存在j中,然后休眠1秒,在这一秒钟主线程做了global++的操作,然后休眠,当到thread_function 调度执行的时候i仍然保存的是1秒钱的值,然后再加加操作,覆盖了global的值。

    所以要加锁

     1 #include <pthread.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <stdio.h>
     5 int myglobal;
     6 pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
     7  void *thread_function(void *arg) {
     8   int i,j;
     9   for ( i=0; i<20; i++) {
    10     pthread_mutex_lock(&mymutex);
    11     j=myglobal;
    12     j=j+1;
    13     printf(".");
    14     fflush(stdout);
    15     sleep(1);
    16     myglobal=j;
    17     pthread_mutex_unlock(&mymutex);
    18   }
    19   return NULL;
    20 }
    21 int main(void) {
    22   pthread_t mythread;
    23   int i;
    24   if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    25     printf("error creating thread.");
    26     abort();
    27   }
    28   for ( i=0; i<20; i++) {
    29     pthread_mutex_lock(&mymutex);
    30     myglobal=myglobal+1;
    31     pthread_mutex_unlock(&mymutex);
    32     printf("o");
    33     fflush(stdout);
    34     sleep(1);
    35   }
    36   if ( pthread_join ( mythread, NULL ) ) {
    37     printf("error joining thread.");
    38     abort();
    39   }
    40   printf("
    myglobal equals %d
    ",myglobal);
    41   exit(0);
    42 }

    打印输出:

    o....................ooooooooooooooooooo
    myglobal equals 40
    

      

    互斥对象是这样工作的。如果线程 a 试图锁定一个互斥对象,而此时线程 b 已锁定了同一个互斥对象时,线程 a 就将进入睡眠状态。一旦线程 b 释放了互斥对象(通过 pthread_mutex_unlock() 调用),线程 a 就能够锁定这个互斥对象(换句话说,线程 a 就将从 pthread_mutex_lock() 函数调用中返回,同时互斥对象被锁定)。同样地,当线程 a 正锁定互斥对象时,如果线程 c 试图锁定互斥对象的话,线程 c 也将临时进入睡眠状态。对已锁定的互斥对象上调用 pthread_mutex_lock() 的所有线程都将进入睡眠状态,这些睡眠的线程将“排队”访问这个互斥对象。

    通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 来保护数据结构。这就是说,通过线程的锁定和解锁,对于某一数据结构,确保某一时刻只能有一个线程能够访问它。可以推测到,当线程试图锁定一个未加锁的互斥对象时,POSIX 线程库将同意锁定,而不会使线程进入睡眠状态。

    所以现在的结果是正确的

    许多互斥对象

    如果放置了过多的互斥对象,代码就没有什么并发性可言,运行起来也比单线程解决方案慢。如果放置了过少的互斥对象,代码将出现奇怪和令人尴尬的错误。幸运的是,有一个中间立场。首先,互斥对象是用于串行化存取*共享数据*。不要对非共享数据使用互斥对象,并且,如果程序逻辑确保任何时候都只有一个线程能存取特定数据结构,那么也不要使用互斥对象。

    其次,如果要使用共享数据,那么在读、写共享数据时都应使用互斥对象。用 pthread_mutex_lock() 和 pthread_mutex_unlock() 把读写部分保护起来,或者在程序中不固定的地方随机使用它们。学会从一个线程的角度来审视代码,并确保程序中每一个线程对内存的观点都是一致和合适的。为了熟悉互斥对象的用法,最初可能要花好几个小时来编写代码,但是很快就会习惯并且*也*不必多想就能够正确使用它们。

  • 相关阅读:
    蓝桥杯java 基础练习 完美的代价
    C# 获得手机归属地功能
    c# HttpWebRequest与HttpWebResponse(转)
    C# Winfrom小黄鸡功能调用
    Winfrom 抓取web页面内容代码
    Winform将网页生成图片
    Winform上传下载文件代码
    Jquery LigerUI框架学习(二)之Tree于Tab标签实现iframe功能
    Jquery LigerUI框架学习(一)
    C# 生成简单验证码
  • 原文地址:https://www.cnblogs.com/diegodu/p/3868253.html
Copyright © 2011-2022 走看看