《UNUX环境高级编程》(11)线程

1、引言

2、线程概念

  • 关于线程和进程的区别:
    https://blog.csdn.net/mu_wind/article/details/124616643
  • 多CPU ,多核和进程以及多线程之间的关系:
    https://blog.csdn.net/jiangxixiaolinzi/article/details/119678291
  • 每个线程都包含有表示执行环境所必须的信息(即每个线程都有属于自己的以下信息,不同线程之间不共享不一致):
    • 线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
  • 一个进程的所有信息对该进程的所有线程都是共享的:
    • 可执行程序的代码、程序的全局内存和堆内存、栈内存以及文件描述符

3、线程标识

  • 每个线程都有一个线程ID。与进程ID不同,进程ID在整个系统中是唯一的,但是线程ID只有在它所属的进程上下文中才有意义
  • 线程ID通过pthread_t数据类型表示。对于该类型的实现不同操作系统不同,Linux是unsigned long int,某些操作系统用一个结构体实现pthread_t。因此不能将pthread_t类型当做整数处理(如用数值的方式进行比较)。因此必须通过一个函数来对两个线程ID进行比较
  • 通过pthread_equal比较两个线程ID
    int pthread_equal(pthread_t t1, pthread_t t2);
    
  • 通过pthread_self函数获得自身线程ID
    pthread_t pthread_self(void);
    

4、线程创建

  • 通过pthread_create函数创建一个线程
    int pthread_create(pthread_t *thread, 
    					const pthread_attr_t *attr, 
    					void *(*start_routine) (void *), 
    					void *arg);
    
    • thread参数:若线程创建成功,保存该线程ID
    • attr:定制各种不同的线程属性。NULL即为默认属性
    • start_routine:线程从该地址开始运行,该函数接受一个void *参数并返回一个void *类型
    • arg:传递给start_routine的参数值。如果需要向start_routine函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
  • 返回值:pthread函数在调用失败是会返回错误码,而不像其他POSIX函数一样设置errno
  • 注意,创建线程时不能保证哪个线程会先执行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
  • 注意,对于pthread_create函数的第一个参数,不能在start_routine线程函数中使用!因为无法保证哪个线程先执行,因此如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的thread成员。应该通过pthread_self获取自身线程ID。
  • 实例:创建了一个线程,打印了进程ID、新线程的线程ID以及初始线程的线程ID
    #include "apue.h"
    #include 
    
    pthread_t ntid;
    
    void
    printids(const char *s)
    {
    	pid_t		pid;
    	pthread_t	tid;
    
    	pid = getpid();
    	/*这里使用pthread_self来获取自己的线程ID,而不是从共享内存或者从线程的启动例程中以参数的形式接收到的。
    	在这个例子中,主线程把新线程ID存放在了ntid中,如果新线程在主线程调用pthread_create之前就返回了,
    	那么显然ntid中的内容并不是正确的线程ID*/
    	tid = pthread_self();
    	printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
    	  (unsigned long)tid, (unsigned long)tid);
    }
    
    void *
    thr_fn(void *arg)
    {
    	printids("new thread: ");
    	return((void *)0);
    }
    
    int
    main(void)
    {
    	int		err;
    
    	err = pthread_create(&ntid, NULL, thr_fn, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread");
    	printids("main thread:");
    	/*为什么主线程要休眠:创建线程时不能保证哪个线程会先执行,如果主线程不休眠,他就可能会退出,
    	这样新线程还没机会运行,整个进程就已经终止了*/
    	sleep(1);
    	exit(0);
    }
    
    命令行输出
    lh@LH_LINUX:~/桌面/apue.3e/threads$ ./threadid 
    main thread: pid 3343 tid 140680582641408 (0x7ff2c027b700)
    new thread:  pid 3343 tid 140680574322432 (0x7ff2bfa8c700)
    
    • 可以看出:两个线程的进程ID相同,但线程ID不同

5、线程终止

  • 如果进程中的任何线程调用了exit_Exit_exit,那么整个进程就会终止

  • 对于某个信号,如果默认的动作是终止进程,那么发送到线程的该信号就会终止整个进程。

  • 单个线程可以通过以下3种方式退出,并且不导致整个进程终止:

    • 线程简单地从启动例程中返回,返回值是线程的退出码
    • 线程可以被同一进程中的其他线程取消
    • 线程调用pthread_exit
  • pthread_exit

    void pthread_exit(void *retval);
    
    • retval是一个void *类型指针,即为线程退出码,进程中的其他线程可以通过pthread_join函数访问这个指针。
  • pthread_join

    int pthread_join(pthread_t thread, void **retval);
    
    • 调用线程一直阻塞,直到指定线程退出(调用pthread_exit、从启动例程中返回或被取消)。retval保存了指定线程return值、pthread_exit参数值、如果线程是被pthread_cancel的则是PTHREAD_CANCELED
    • 如果线程处于分离状态(pthread_detach),则pthread_join失败,返回EINVAL
  • 实例:获取已终止线程的退出码。

    #include "apue.h"
    #include 
    
    void *
    thr_fn1(void *arg)
    {
    	printf("thread 1 returning\n");
    	/*线程退出方式1:简单地从启动例程中返回*/
    	return((void *)1);
    }
    
    void *
    thr_fn2(void *arg)
    {
    	printf("thread 2 exiting\n");
    	/*线程退出方式2:线程调用pthread_exit*/
    	pthread_exit((void *)2);
    }
    
    int
    main(void)
    {
    	int			err;
    	pthread_t	tid1, tid2;
    	void		*tret;
    
    	err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread 1");
    	err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread 2");
    	err = pthread_join(tid1, &tret);
    	if (err != 0)
    		err_exit(err, "can't join with thread 1");
    	printf("thread 1 exit code %ld\n", (long)tret);
    	err = pthread_join(tid2, &tret);
    	if (err != 0)
    		err_exit(err, "can't join with thread 2");
    	printf("thread 2 exit code %ld\n", (long)tret);
    	exit(0);
    }
    

    命令行输出:

    lh@LH_LINUX:~/桌面/apue.3e/threads$ ./exitstatus 
    thread 1 returning
    thread 2 exiting
    thread 1 exit code 1
    thread 2 exit code 2
    
    • 可以看出,当一个线程通过调用pthread_exit退出或者简单地从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。
    • 注意:pthread_join可以使调用线程一直阻塞,直到指定线程退出,所以不用担心同步问题。
  • 实例:给出了局部变量(分配在栈上)作为pthread_exit的参数时出现的问题。

    #include "apue.h"
    #include 
    
    struct foo {
    	int a, b, c, d;
    };
    
    void
    printfoo(const char *s, const struct foo *fp)
    {
    	printf("%s", s);
    	printf("  structure at 0x%lx\n", (unsigned long)fp);
    	printf("  foo.a = %d\n", fp->a);
    	printf("  foo.b = %d\n", fp->b);
    	printf("  foo.c = %d\n", fp->c);
    	printf("  foo.d = %d\n", fp->d);
    }
    
    void *
    thr_fn1(void *arg)
    {
    	struct foo	foo = {1, 2, 3, 4};
    
    	printfoo("thread 1:\n", &foo);
    	pthread_exit((void *)&foo);
    }
    
    void *
    thr_fn2(void *arg)
    {
    	printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());
    	pthread_exit((void *)0);
    }
    
    int
    main(void)
    {
    	int			err;
    	pthread_t	tid1, tid2;
    	struct foo	*fp;
    
    	err = pthread_create(&tid1, NULL, thr_fn1, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread 1");
    	err = pthread_join(tid1, (void *)&fp);
    	if (err != 0)
    		err_exit(err, "can't join with thread 1");
    	sleep(1);
    	printf("parent starting second thread\n");
    	err = pthread_create(&tid2, NULL, thr_fn2, NULL);
    	if (err != 0)
    		err_exit(err, "can't create thread 2");
    	sleep(1);
    	printfoo("parent:\n", fp);
    	exit(0);
    }
    

    命令行输出:

    lh@LH_LINUX:~/桌面/apue.3e/threads$ ./badexit2 
    thread 1:
      structure at 0x7f7355e59f30
      foo.a = 1
      foo.b = 2
      foo.c = 3
      foo.d = 4
    parent starting second thread
    thread 2: ID is 140133339080448
    parent:
      structure at 0x7f7355e59f30
      foo.a = 1445050624
      foo.b = 32627
      foo.c = 1442644546
      foo.d = 32627
    
    • 可以看到,当主线程访问这个结构时,结构的内容(在线程tid1的栈上分配的)已经改变了。注意第二个线程(tid2)的栈是如何覆盖第一个线程的。为了解决这个问题,可以使用全局结构,或者用malloc函数分配结构。
  • pthread_cancel
    通过pthread_cancel函数来请求取消同一进程中的其他线程。

    int pthread_cancel(pthread_t thread);
    
    • 该函数会使得thread标识的线程行为如同调用了pthread_exit(PTHREAD_CANCEL),但是线程可以选择忽略取消或者控制如何被取消。
    • 注意pthread_cancel并不等待线程终止,仅仅提出请求,而该请求可能会被忽略。
  • 线程清理处理程序

    • 线程可以安排它退出时需要调用的函数,这与进程在退出时可以通过atexit函数安排退出类似。
    • 一个线程可以建立多个清理处理程序,处理程序记录在栈中,因此它们的执行顺序与它们注册时相反。
      void pthread_cleanup_push(void (*routine)(void *), void *arg);
      void pthread_cleanup_pop(int execute);
      
    • pthread_cleanup_push
      • 其中arg参数即为传递给routine线程清理处理程序的参数值
      • 线程清理处理程序在以下情况下才会被调用:
        • 调用pthread_exit
        • 响应取消请求(pthread_cancel
        • 用非零参数调用pthread_cleanup_pop
      • 如果线程是从启动例程中返回而终止(return语句),那么不会调用线程清理处理程序。
    • pthread_cleanup_pop
      • 该函数将删除上次pthread_cleanup_push建立的线程清理处理程序。但是如果execute参数设置为0,清理函数将不被调用。
      • 注意,pthread_cleanup_pushpthread_cleanup_pop必须成对出现
      • 因为这两个函数可以被实现为宏,其中pthread_cleanup_push包含{pthread_cleanup_pop包含},因此:
        • pushpop一定是成对出现的
        • push可以有多个,同样的pop也要对应的数量,遵循"先进后出原则"。
  • 给出了一个如何使用线程处理程序的例子

    #include "apue.h"
    #include 
    
    void
    cleanup(void *arg)
    {
    	printf("cleanup: %s\n", (char *)arg);
    }
    
    void *
    thr_fn1(void *arg)
    {
    	printf("thread 1 start\n");
    	/*建立清理处理程序*/
    	pthread_cleanup_push(cleanup, "thread 1 first handler");
    	pthread_cleanup_push(cleanup, "thread 1 second handler");
    	printf("thread 1 push complete\n");
    	if (arg)
    		/*从启动例程中返回而终止,清理处理程序不会被调用*/
    		return((void *)1);
    	/*传递参数0,将不调用清理函数,那为什么还要调用pthread_cleanup_pop?
    	因为pthread_cleanup_pop需要和pthread_cleanup_push匹配起来,否则可能编译不通过*/
    	pthread_cleanup_pop(0);
    	pthread_cleanup_pop(0);
    	return((void *)1);
    }
    
    void *
    thr_fn2(void *arg)
    {
    	printf("thread 2 start\n");
    	pthread_cleanup_push(cleanup, "thread 2 first handler");
    	pthread_cleanup_push(cleanup, "thread 2 second handler");
    	printf("thread 2 push complete\n");
    	if (arg)
    		/*调用pthread_exit后,清理处理程序被调用*/
    		pthread_exit((void *)2);
    	pthread_cleanup_pop(0);
    	pthread_cleanup_pop(0);
    	pthread_exit((void *)2);
    }
    
    int
    main(void)
    {
    	int			err;
    	pthread_t	tid1, tid2;
    	void		*tret;
    
    	err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    	if (err != 0)
    		err_exit(err, "can't create thread 1");
    	err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    	if (err != 0)
    		err_exit(err, "can't create thread 2");
    	err = pthread_join(tid1, &tret);
    	if (err != 0)
    		err_exit(err, "can't join with thread 1");
    	printf("thread 1 exit code %ld\n", (long)tret);
    	err = pthread_join(tid2, &tret);
    	if (err != 0)
    		err_exit(err, "can't join with thread 2");
    	printf("thread 2 exit code %ld\n", (long)tret);
    	exit(0);
    }
    

    命令行输出:

    lh@LH_LINUX:~/桌面/apue.3e/threads$ ./cleanup 
    thread 2 start
    thread 2 push complete
    thread 1 start
    thread 1 push complete
    thread 1 exit code 1
    cleanup: thread 2 second handler
    cleanup: thread 2 first handler
    thread 2 exit code 2
    
    • 两个线程都正确地启动和退出了,但是只有第二个线程的清理处理程序被调用了,理由在代码中已经说明。
    • 注意:从命令行输出可以看到清理处理程序是按照与他们安装时相反的顺序被调用的。
  • pthread_detach

    • 默认情况下,线程的终止状态会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被收回。在线程被分离后,我们不能用pthread_join获取它的终止状态。
    • 使用pthread_detach函数分离线程
      int pthread_detach(pthread_t thread);
      

6、线程同步

  • 学习操作系统的时候,同步的概念比较熟悉了,这里直接展示线程同步的例子。
  • 下图中,线程A读取变量然后给这个变量赋予一个新的数值,但写操作需要两个存储器周期。当线程B在两个存储器写周期中读取这个变量时,它就会得到不一致的值。
    《UNUX环境高级编程》(11)线程_第1张图片
  • 为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量。下图描述了这个同步。如果线程B希望读取变量,它首先要获取。同样,当线程A更新变量时,也需要获取同样的这把锁。这样,线程B在线程A释放锁以前就不能读取变量。
    《UNUX环境高级编程》(11)线程_第2张图片
  • 比如两个或多个线程试图在同一时间修改同一变量时,也需要进行同步。对一个数值进行增量操作通常分解为以下三步:
    • 从内存单元读入寄存器
    • 在寄存器中对该变量做增量操作
    • 把新值写回内存单元
  • 如果不进行线程同步(即对数值进行增量操作不是原子的),将会导致结果错误:
    《UNUX环境高级编程》(11)线程_第3张图片
  • 除了计算机体系结构外,程序使用变量的方式也会引起竞争,也会导致不一致的情况发生。

6.1、互斥量

  • 互斥量mutex本质是一把锁,在访问共享资源前对互斥量加锁,在访问完成后解锁互斥量。
  • 互斥量用pthread_mutex_t数据类型表示,在使用之前必须进行初始化。如果是静态分配的互斥量,可以将它设置为PTHREAD_MUTEX_INITIALIZER进行初始化。也可以通过pthread_mutex_init函数初始化。如果是动态分配的互斥量(如用malloc分配),则在释放内存前需要调用pthread_mutex_destroy
    /*成功则返回0,错误则返回编号*/
    int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);
    int pthread_mutex_destroy(pthread_mutex_t* mutex);
    
    • 要用默认属性初始化互斥量,只需要把attr设为NULL
  • 通过以下函数对互斥量加锁解锁
    int pthread_mutex_lock (pthread_mutex_t *__mutex);
    int pthread_mutex_trylock (pthread_mutex_t *__mutex);
    int pthread_mutex_unlock (pthread_mutex_t *__mutex)
    
    • pthread_mutex_lock:对互斥量加锁。如果互斥量已经上锁,则调用线程将阻塞直到互斥量被解锁
    • pthread_mutex_trylock:对互斥量尝试加锁,如果调用该函数时互斥量处于未锁状态,则锁住互斥量并返回0;如果互斥量处于加锁状态,则不阻塞直接返回EBUSY
    • pthread_mutex_unlock:对互斥量解锁
  • 实例:描述了用于保护某个数据结构的互斥量。当一个以上的线程需要访问动态分配的对象时,我们可以在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据之前,该对象内存空间不会被释放。
    #include 
    #include 
    
    struct foo {
    	int             f_count;
    	pthread_mutex_t f_lock;
    	int             f_id;
    	/* ... more stuff here ... */
    };
    
    struct foo *
    foo_alloc(int id) /* allocate the object */
    {
    	struct foo *fp;
    	/*没必要加锁,在这个操作之前分配线程是唯一引用该对象的线程。
    	如果在这之后要将该对象放到一个列表中,那就有可能被其他线程发现,需要加锁。*/
    	if ((fp = malloc(sizeof(struct foo))) != NULL) {
    		fp->f_count = 1;
    		fp->f_id = id;
    		if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
    			free(fp);
    			return(NULL);
    		}
    		/* ... continue initialization ... */
    	}
    	return(fp);
    }
    
    /*线程需要调用foo_hold对这个对象的引用计数加1。*/
    void
    foo_hold(struct foo *fp) /* add a reference to the object */
    {
    	pthread_mutex_lock(&fp->f_lock);
    	fp->f_count++;
    	pthread_mutex_unlock(&fp->f_lock);
    }
    
    /*当对象使用完毕时,必须调用foo_rele释放引用。如果有另一个线程在在调用foo_hold时
    阻塞等待互斥锁,这时即使该队形引用计数为0,fool_rele释放该对象的内存仍然是不对的。
    可以通过确保在释放内存前不会被找到 这种方式来避免上述问题。*/
    void
    foo_rele(struct foo *fp) /* release a reference to the object */
    {
    	pthread_mutex_lock(&fp->f_lock);
    	/*当最后一个引用被释放时,对象所占的内存空间就被释放*/
    	if (--fp->f_count == 0) { /* last reference */
    		pthread_mutex_unlock(&fp->f_lock);
    		pthread_mutex_destroy(&fp->f_lock);
    		free(fp);
    	} else {
    		pthread_mutex_unlock(&fp->f_lock);
    	}
    }
    

6.2、互斥量

  • 产生死锁的条件

    • 线程试图对同一个互斥量 加锁两次
    • 程序中使用一个以上的互斥量时,如果允许线程一直占有第一个互斥量,并且在试图锁住第二个互斥量时处于阻塞状态,但拥有第二个互斥量的线程也试图锁住第一个互斥量。因为两个线程都在相互请求另一个线程拥有的资源,所以两个线程都无法向前运行。
  • 如何避免死锁?

    • 仔细控制互斥量加锁的顺序来避免死锁的发生。例如:假设需要对两个互斥量A和B同时加锁。所有线程总是在对互斥量B加锁之前锁住互斥量A,或者反之,那么就不会发生死锁。
  • 实例:更新6.1节中的程序,展示了两个互斥量的使用方法。在同时需要两个互斥量时,总是让他们以相同的顺序加锁,这样可以避免死锁。此外,本例涉及散列表相关知识,详见散列表。本例中使用的散列函数的构造方法是除留余数法,哈希碰撞的处理基于拉链法

    #include 
    #include 
    
    #define NHASH 29
    /*定义散列函数:除留余数法*/
    #define HASH(id) (((unsigned long)id)%NHASH)
    /*定义一个散列表*/
    struct foo *fh[NHASH];
    /*初始化互斥量hashlock*/
    pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
    /*定义散列链的各字段*/
    struct foo {
    	int             f_count;
    	pthread_mutex_t f_lock;
    	int             f_id;
    	struct foo     *f_next; /* protected by hashlock */
    	/* ... more stuff here ... */
    };
    
    struct foo *
    foo_alloc(int id) /* allocate the object */
    {
    	struct foo	*fp;
    	int			idx;
    
    	if ((fp = malloc(sizeof(struct foo))) != NULL) {
    		fp->f_count = 1;
    		fp->f_id = id;
    		/*初始化互斥量fp->f_lock*/
    		if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
    			free(fp);
    			return(NULL);
    		}
    		/*通过key获取散列值*/
    		idx = HASH(id);
    		/*锁定互斥量hashlock,可以看出它用于保护散列链字段f_next和散列表fh。
    		加锁的原因:在这之后要将该对象放到一个列表中,那就有可能被其他线程发现,需要加锁。*/
    		pthread_mutex_lock(&hashlock);
    		/*维护散列链*/
    		fp->f_next = fh[idx];
    		fh[idx] = fp;
    		/*锁定互斥量f_lock,它用于保护对foo结构中的其他字段的访问*/
    		pthread_mutex_lock(&fp->f_lock);
    		pthread_mutex_unlock(&hashlock);
    		/* ... continue initialization ... */
    		pthread_mutex_unlock(&fp->f_lock);
    	}
    	return(fp);
    }
    
    void
    foo_hold(struct foo *fp) /* add a reference to the object */
    {
    	pthread_mutex_lock(&fp->f_lock);
    	fp->f_count++;
    	pthread_mutex_unlock(&fp->f_lock);
    }
    
    /*搜索被请求的结构,如果找到了,那么就增加其引用计数,并返回该结构的指针*/
    struct foo *
    foo_find(int id) /* find an existing object */
    {
    	struct foo	*fp;
    	/*锁住散列列表锁,然后搜索被请求的结构。注意,加锁的顺序是:
    	先锁定散列列表锁hashlock,再通过调用foo_hold锁定f_lock互斥量。
    	这样就与foo_alloc中的操作保持了一致。*/
    	pthread_mutex_lock(&hashlock);
    	for (fp = fh[HASH(id)]; fp != NULL; fp = fp->f_next) {
    		if (fp->f_id == id) {
    			foo_hold(fp);
    			break;
    		}
    	}
    	pthread_mutex_unlock(&hashlock);
    	return(fp);
    }
    
    void
    foo_rele(struct foo *fp) /* release a reference to the object */
    {
    	struct foo	*tfp;
    	int			idx;
    	/*如果这是最后一个引用,就需要对这个结构互斥量进行解锁,因为我们需要从散列列表中删除这个结构,这样才可以获取散列列表锁,然后重新获取结构互斥量。*/
    	pthread_mutex_lock(&fp->f_lock);
    	if (fp->f_count == 1) { /* last reference */
    		pthread_mutex_unlock(&fp->f_lock);
    		pthread_mutex_lock(&hashlock);
    		pthread_mutex_lock(&fp->f_lock);
    		/* need to recheck the condition */
    		/*从上一次获得结构互斥量以来我们可能被阻塞着,所以需要重新
    		检查条件,判断是否还需要释放这个结构*/
    		if (fp->f_count != 1) {
    			fp->f_count--;
    			pthread_mutex_unlock(&fp->f_lock);
    			pthread_mutex_unlock(&hashlock);
    			return;
    		}
    		/* remove from list */
    		idx = HASH(fp->f_id);
    		tfp = fh[idx];
    		if (tfp == fp) {
    			fh[idx] = fp->f_next;
    		} else {
    			while (tfp->f_next != fp)
    				tfp = tfp->f_next;
    			tfp->f_next = fp->f_next;
    		}
    		pthread_mutex_unlock(&hashlock);
    		pthread_mutex_unlock(&fp->f_lock);
    		pthread_mutex_destroy(&fp->f_lock);
    		free(fp);
    	} else {
    		/*如果不是最后一个引用,那么只需要简单地对整个引用计数减1,
    		并解锁所有互斥量*/
    		fp->f_count--;
    		pthread_mutex_unlock(&fp->f_lock);
    	}
    }
    

    可以看出foo_rele函数设计地十分复杂,为了简化代码,我们可以在一开始就使用hashlock,代码更正如下:

    void
    foo_rele(struct foo *fp) /* release a reference to the object */
    {
    	struct foo	*tfp;
    	int			idx;
    
    	pthread_mutex_lock(&hashlock);
    	if (--fp->f_count == 0) { /* last reference, remove from list */
    		idx = HASH(fp->f_id);
    		tfp = fh[idx];
    		if (tfp == fp) {
    			fh[idx] = fp->f_next;
    		} else {
    			while (tfp->f_next != fp)
    				tfp = tfp->f_next;
    			tfp->f_next = fp->f_next;
    		}
    		pthread_mutex_unlock(&hashlock);
    		pthread_mutex_destroy(&fp->f_lock);
    		free(fp);
    	} else {
    		pthread_mutex_unlock(&hashlock);
    	}
    }
    
    • 通过该例子可以发现以下特性:
      • 如果锁的粒度太粗,就会出现很多线程阻塞等待相同的锁,着并不能改善并发性。
      • 如果锁的粒度太细,那么过多的锁的开销会使系统性能受到影响,并且代码变得复杂
    • 所以,需要在满足锁的需求的情况下,在代码复杂性性能之间找到平衡。

6.3、函数pthread_mutex_timelock

  • 可以通过pthread_mutex_timelock指定阻塞时间
    int pthread_mutex_timedlock (pthread_mutex_t * mutex, const struct timespec *abstime)
    
    • 该函数介于pthread_mutex_lockpthread_mutex_trylock之间
    • 注意abstime指定的时间是绝对时间而不是相对时间
    • 在到达超时时间abstime后,该函数不会对互斥量加锁,而是返回错误ETIMEDOUT

6.4、

你可能感兴趣的:(UNIX环境高级编程,java,jvm,redis)