首页 > C/C++ > 线程 > Linux多线程编程(七)读写锁
2017
08-16

Linux多线程编程(七)读写锁

七、 读写锁

  1、 基本概念

  2、 API

    1)、 初始化读写锁

    2)、 在读写锁上获取读锁(读锁定)

    3)、 在指定的时间之前在读写锁上获取读锁(读锁定)

    4)、 尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

    5)、 在读写锁上获取写锁(写锁定)

    6)、 在指定的时间之前在读写锁上获取写锁(写锁定)

    7)、 尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

    8)、 尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

    9)、 初始化读写锁属性

    10)、 销毁读写锁属性

    11)、 设置/获取读写锁的作用范围属性

 

 

1、基本概念

当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。因而有时候将读和写访问区分开来是有益处的。
读写锁就提供了这样的能力。通过读写锁,可以对受保护的共享资源进行并发读取和独占写入。其规则为:

  1. 只要没有线程持有某个特定的读写锁用于写,那么任意数目的线程都可以持有该读写锁进行读
  2. 仅当没有任何线程持有某个特定的读写锁用于写或读时,才能分配该读写锁给某个线程让其进行写

读写锁适用于有很多并发读请求,同时写比较少的场景。由于读写锁允许多个读者并存因而提高了并发性;同时由于任意时刻只有一个写者可以持有读写锁,又可以保护数据在写期间不被其它读者或写者所干扰。

2、API

POSIX定义的读写锁的数据类型是: pthread_rwlock_t

  1. #include <pthread.h>  
  2. int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); 成功返回0,其它返回值表示出错  
  3. int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock ); 成功返回0,其它返回值表示出错  
  4. int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abs_timeout); 成功返回0,其它返回值表示出错  
  5. int pthread_rwlock_reltimedrdlock_np(pthread_rwlock_t *rwlock, const struct timespec *abs_timeout); 成功返回0,其它返回值表示出错  
  6. int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); 成功返回0,其它返回值表示出错  
  7. int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock ); 成功返回0,其它返回值表示出错  
  8. int  pthread_rwlock_timedwrlock(pthread_rwlock_t  *rwlock, const struct timespec *abs_timeout); 成功返回0,其它返回值表示出错  
  9. int pthread_rwlock_reltimedwrlock_np(pthread_rwlock_t *rwlock, const struct timespec *abs_timeout); 成功返回0,其它返回值表示出错  
  10. int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 成功返回0,其它返回值表示出错  
  11. int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); 成功返回0,其它返回值表示出错  
  12. int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 成功返回0,其它返回值表示出错  
  13. int pthread_rwlockattr_init(pthread_rwlockattr_t *attr); 成功返回0,其它返回值表示出错  
  14. int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr); 成功返回0,其它返回值表示出错  
  15. int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared); 成功返回0,其它返回值表示出错  
  16. int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared); 成功返回0,其它返回值表示出错  

1)、初始化读写锁

如果读写锁变量是静态的则可以直接用PTHREAD_RWLOCK_INITIALIZER来初始化它,比如:
static pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER
如果读写锁变量是动态分配的,则必须在使用它之前用pthread_rwlock_init来初始化它。
pthread_rwlock_init用来初始化rwlock所指向的读写锁,如果attr为NULL则会使用缺省的属性初始化读写锁,否则使用指定的attr初始化读写锁。
使用PTHREAD_RWLOCK_INITIALIZER 宏与动态分配具有null 属性的 pthread_rwlock_init等效,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。

2)、在读写锁上获取读锁(读锁定)

pthread_rwlock_rdlock用来读锁定指定的读写锁。

  1. 如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
  2. 如果没有写者持有该锁,但是有写者阻塞在该锁上,则调用线程是否能获取该锁是不确定的。
  3. 如果有写者持有该锁,则调用线程无法获取该锁。
  4. 如果在进行调用时,调用线程已经写锁定了该锁,则结果是不确定的。
  5. 如果调用pthread_rwlock_rdlock时所指定的读写锁未初始化,则结果是不确定的。

如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。为避免写者饥饿,允许将写者的优先级设置的高于读者。
一个线程可以在一个读写锁上多次执行读锁定。线程可以成功调用pthread_rwlock_rdlock n 次,但是之后该线程必须调用 pthread_rwlock_unlock() n 次才能解除锁定。
信号处理程对这里的等待是透明的:线程信号处理程序可以处理传送给等待读写锁的线程的信号。从信号处理程序返回后,线程将继续等待读写锁,就好像线程未被中断一样。

3)、在指定的时间之前在读写锁上获取读锁(读锁定)

pthread_rwlock_timedrdlock用来读锁定指定的读写锁,如果必须等待以获取读锁,则最多等待指定的时长后就不再等待。
如果锁可以立即被获得,那么时间参数就不会被检查。
信号处理程对这里的等待是透明的:如果等待被信号处理程序打断了,则从信号处理程序返回后,会继续等待就像没有被打断一样。
如果在进行调用时,调用线程已经写锁定了该锁则可能导致死锁。
pthread_rwlock_reltimedrdlock_np与pthread_rwlock_timedrdlock基本相同,它们唯一的区别在于前者使用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。

4)、尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

pthread_rwlock_tryrdlock用于尝试以非阻塞的方式来在读写锁上获取读锁。如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。

5)、在读写锁上获取写锁(写锁定)

pthread_rwlock_wrlock用来写锁定指定的读写锁。

  1. 如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。否则线程阻塞。
  2. 如果在进行调用时,调用线程已经写锁定或读锁定了该锁,则结果是不确定的。
  3. 如果调用pthread_rwlock_rdlock时所指定的读写锁未初始化,则结果是不确定的。

如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。为避免写者饥饿,允许将写者的优先级设置的高于读者。
一个线程可以在一个读写锁上多次执行读锁定。线程可以成功调用pthread_rwlock_rdlock n 次,但是之后该线程必须调用 pthread_rwlock_unlock() n 次才能解除锁定。
信号处理程对这里的等待是透明的:线程信号处理程序可以处理传送给等待读写锁的线程的信号。从信号处理程序返回后,线程将继续等待读写锁,就好像线程未中断一样。

6)、在指定的时间之前在读写锁上获取写锁(写锁定)

pthread_rwlock_timedwrlock用来写锁定指定的读写锁,如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁,否则将等待,但是最多等待指定的时长后就不再等待。
pthread_rwlock_reltimedwrlock_np与pthread_rwlock_timedwrlock基本相同,它们唯一的区别在于前者使用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。

7)、尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

pthread_rwlock_trywrlock用于尝试以非阻塞的方式来在读写锁上获取写锁。如果有任何的读者或写者持有该锁,则立即失败返回。

8)、尝试在读写锁上以非阻塞的方式获取读锁(读锁定)

pthread_rwlock_destroy用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由pthread_rwlock_init自动申请的资源)

9)、初始化读写锁属性

pthread_rwlockattr_init使用缺省的属性来初始化读写锁属性指定的读写锁属性对象。
如果指定的读写锁属性对象已经初始化,则结果是不确定的。

10)、销毁读写锁属性

pthread_rwlockattr_destroy可用来销毁读写锁属性对象。
在再次调用 pthread_rwlockattr_init重新初始化该对象之前,使用该读写锁的结果是不确定的。

11)、设置/获取读写锁的作用范围属性

pthread_rwlockattr_setpshared用来设置读写锁的作用范围,设置的值存放在指定的读写锁属性对象中。
作用范围的取值及其含义:

  • PTHREAD_PROCESS_SHARED:该读写锁可以在多个进程中的线程之间共享。
  • PTHREAD_PROCESS_PRIVATE:仅初始化本读写锁的线程所在的进程内的线程才能够使用该读写锁。

pthread_rwlockattr_getpshared用来获取指定的读写锁属性对像上的作用范围属性。

 

实例1:

/**/

  1.  
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <pthread.h>
  7. #include <errno.h>
  8. #define MAXDATA     1024
  9. #define MAXREDER    100
  10. #define MAXWRITER   100
  11.  
  12. struct
  13. {
  14.     pthread_rwlock_t   rwlock;   //读写锁
  15.     char datas[MAXDATA];          //共享数据域
  16. }shared = {
  17.     PTHREAD_RWLOCK_INITIALIZER
  18. };
  19. void *reader(void *arg);
  20. void *writer(void *arg);
  21. int main(int argc,char *argv[])
  22. {
  23.     int i,readercount,writercount;
  24.     pthread_t tid_reader[MAXREDER],tid_writer[MAXWRITER];
  25.     if(argc != 3)
  26.     {
  27.         printf("usage : <reader_writer> #<readercount> #<writercount>\n");
  28.         exit(0);
  29.     }
  30.     readercount = atoi(argv[1]);  //读者个数
  31.     writercount = atoi(argv[2]);   //写者个数
  32.     pthread_setconcurrency(readercount+writercount);
  33.     for(i=0;i<writercount;++i)
  34.         pthread_create(&tid_writer[i],NULL,writer,NULL);
  35.     sleep(1); //等待写者先执行
  36.     for(i=0;i<readercount;++i)
  37.         pthread_create(&tid_reader[i],NULL,reader,NULL);
  38.     //等待线程终止
  39.     for(i=0;i<writercount;++i)
  40.         pthread_join(tid_writer[i],NULL);
  41.     for(i=0;i<readercount;++i)
  42.         pthread_join(tid_reader[i],NULL);
  43.     exit(0);
  44. }void *reader(void *arg)
  45. {
  46.     pthread_rwlock_rdlock(&shared.rwlock);  //获取读出锁
  47.     printf("Reader begins read message.\n");
  48.     printf("Read message is: %s\n",shared.datas);
  49.     pthread_rwlock_unlock(&shared.rwlock);  //释放锁
  50.     return NULL;
  51. }
  52. void *writer(void *arg)
  53. {
  54.     char datas[MAXDATA];
  55.     pthread_rwlock_wrlock(&shared.rwlock);  //获取写如锁
  56.     printf("Writers begings write message.\n");
  57.     printf("Enter the write message: \n");
  58.     gets(datas);   //写入数据   
  59.     strcat(shared.datas,datas);
  60.     pthread_rwlock_unlock(&shared.rwlock);  //释放锁
  61.     return NULL;
  62. }
  63.  

 

运行结果为:

  1. vteyga@root:/root>./pthread_rwlock1 3 1
  1. Writers begings write message.
  2. Enter the write message:
  3. abc
  4. Reader begins read message.
  5. Reader begins read message.
  6. Read message is: abc
  7. Reader begins read message.
  8. Read message is: abc
  9. Read message is: abc

 

实例2:
/**/
1.    #include <stdio.h>
2.    #include <stdlib.h>
3.    #include <string.h>
4.    #include <unistd.h>
5.    #include <pthread.h>
6.    
7.    /* 初始化读写锁 */
8.    pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
9.    /* 全局资源 */
10.    int global_num = 10;
11.    
12.    void err_exit(const char *err_msg)
13.    {
14.        printf("error:%s\n", err_msg);
15.        exit(1);
16.    }
17.    
18.    /* 读锁线程函数 */
19.    void *thread_read_lock(void *arg)
20.    {
21.        char *pthr_name = (char *)arg;
22.    
23.        while (1)
24.        {
25.            /* 读加锁 */
26.            pthread_rwlock_rdlock(&rwlock);
27.    
28.            printf("线程%s进入临界区,global_num = %d\n", pthr_name, global_num);
29.            sleep(1);
30.            printf("线程%s离开临界区…\n", pthr_name);
31.    
32.            /* 读解锁 */
33.            pthread_rwlock_unlock(&rwlock);
34.    
35.            sleep(1);
36.        }
37.    
38.        return NULL;
39.    }
40.    
41.    /* 写锁线程函数 */
42.    void *thread_write_lock(void *arg)
43.    {
44.        char *pthr_name = (char *)arg;
45.    
46.        while (1)
47.        {
48.            /* 写加锁 */
49.            pthread_rwlock_wrlock(&rwlock);
50.    
51.            /* 写操作 */
52.            global_num++;
53.            printf("线程%s进入临界区,global_num = %d\n", pthr_name, global_num);
54.            sleep(1);
55.            printf("线程%s离开临界区…\n", pthr_name);
56.    
57.            /* 写解锁 */
58.            pthread_rwlock_unlock(&rwlock);
59.    
60.            sleep(2);
61.        }
62.    
63.        return NULL;
64.    }
65.    
66.    int main(void)
67.    {
68.        pthread_t tid_read_1, tid_read_2, tid_write_1, tid_write_2;
69.    
70.        /* 创建4个线程,2个读,2个写 */
71.        if (pthread_create(&tid_read_1, NULL, thread_read_lock, "read_1") != 0)
72.            err_exit("create tid_read_1");
73.    
74.        if (pthread_create(&tid_read_2, NULL, thread_read_lock, "read_2") != 0)
75.            err_exit("create tid_read_2");
76.    
77.        if (pthread_create(&tid_write_1, NULL, thread_write_lock, "write_1") != 0)
78.            err_exit("create tid_write_1");
79.    
80.        if (pthread_create(&tid_write_2, NULL, thread_write_lock, "write_2") != 0)
81.            err_exit("create tid_write_2");
82.    
83.        /* 随便等待一个线程,防止main结束 */
84.        if (pthread_join(tid_read_1, NULL) != 0)
85.            err_exit("pthread_join()");
86.    
87.        return 0;
88.    }
89.    

运行结果为:
1.    vteyga@root:/root>./pthread_rwlock2
2.    线程read_1进入临界区,global_num = 10
3.    线程read_2进入临界区,global_num = 10
4.    线程read_1离开临界区…
5.    线程read_2离开临界区…
6.    线程write_2进入临界区,global_num = 11
7.    线程write_2离开临界区…
8.    线程write_1进入临界区,global_num = 12
9.    线程write_1离开临界区…
10.    线程read_1进入临界区,global_num = 12
11.    线程read_2进入临界区,global_num = 12
12.    线程read_2离开临界区…
13.    线程read_1离开临界区…
14.    线程write_2进入临界区,global_num = 13
15.    线程write_2离开临界区…
16.    线程write_1进入临界区,global_num = 14
17.    线程write_1离开临界区…
18.     
 

 

最后编辑:
作者:liujg
真实-不弄虚,不做假,做自己,不违心; 踏实-不浮躁,不盲从,不急功,不近利; 实学-不投机,不取巧,勤于学,精于业。