之前在线程高级操作中说到了线程的高级操作包括修改线程的属性和进行线程之间的同步操作。线程的同步有两种方式,一种是使用互斥量一种是使用读写锁。上一篇文章说的是互斥量,这篇文章主要介绍的是读写锁。
读写锁与互斥量类似,但是读写锁相对于互斥量来说最大的特点就是并行性高。互斥锁每次只有一个线程可以得到锁进行操作,其他的线程处于阻塞状态。多线程的意义就在于高并发性,但是使用互斥量就会将并行操作编程串行操作,程序的效率会大打折扣。
读写锁的优点是读写锁分为两种,读线程和写线程。读线程只对共享资源进行读操作,不修改资源,写操作对共享资源进行写操作,改变共享资源。在执行程序是,如果对于读操作的线程远远大于写线程的时候,使用读写锁可以提高线程的并发。
1.初始化读写锁
在Linux环境下使用pthread_rwlock_t表示读写锁,使用pthread_rwlock_init()函数对读写锁进行初始化。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restic attr) 函数:
头文件: #include <pthread.h>
参数:第一个参数rwlock是读写锁的指针,读写锁在该函数内被初始化,通过该参数返回给调用者。第二个参数是读写锁的属性,一般设置为NULL,系统默认对其进行初始化。
返回值:成功返回0,失败返回错误号。
函数功能:初始化一个读写锁,初始成功之后,第一个参数rwlock就是一个读写锁指针,可供其他进程使用。
当一个读写锁不在使用的时候,需要销毁读写锁,Linux环境下使用 pthread_rwlock_destroy() 函数销毁一个读写锁。
pthread_rwlock_destroy(pthread_rwlock_t *rwlock) 函数:
头文件: #include <pthread.h>
参数:参数rwlock是要销毁的读写锁的指针。
返回值:成功返回0,失败返回错误号。
函数功能:销毁不用的读写锁。
2.得到与释放读写锁
得到读写锁:
pthread_rwlock_rdlock(pthread_rwlock_t * rwlock) 函数:函数参数是一个读写锁,如果该读写锁已经被某一线程在读模式下使用,该线程还可以用到该锁。如果该读写锁已经被某一线程在写模式下使用,此时会出现线程阻塞直到读写锁被释放。
pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock) 函数: 该函数的返回值和参数与pthread_rwlock_rdlock一样,唯一不同的是pthread_rwlock_tryrdlock()函数得不到读写锁的时候,会返回一个错误编号BUSSY,不会导致阻塞。
同样的,可以理解 pthread_rwlock_wrlock(pthread_rwlock_t * rwlock) 函数和 pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock) 函数的用法。
释放读写锁:
pthread_rwlock_unlock() 函数释放读写锁。
3.使用读写锁实例
之前的线程高级操作(一)中我们用互斥量操作一个链表,这次用读写锁来操作一个列表,而且可以通过两个程序的对比来比较读写锁与互斥量的效率的对比。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <sys/time.h> #include <time.h> typedef struct job * Job; /* 链表结点结构 */ struct job{ pthread_t tid; /* 线程ID */ Job next; /* 下一个链表结点 */ int val; /* 结点值 */ }; struct timeval begintime; int insert(Job head, int val, pthread_t tid) { Job p, q; p = head; /* 头指针 */ if(p != NULL){ /* 判断空链表的情况 */ while(p->next != NULL){ p = p->next; } } q = (Job)malloc(sizeof(struct job)); /* 为结点分配内存空间 */ if(q == NULL){ perror("fail to malloc"); return -1; } q->next = NULL; q->val = val; q->tid = tid; /* 设置结点的所有者,线程1 */ if(p == NULL){ /* 设置链表头指针 */ head = q; return 0; } p->next = q; /* 插入到队列中 */ return 0; } int free_job(Job head) { Job p,q; for(p = head; p != NULL; p = p->next){ /* 线程退出时释放所有的任务结点 */ q = p; free(q); } return 0; } void print(Job head) { Job p; for(p = head->next; p != NULL; p = p->next) /* 输出取得的任务列表 */ printf("thread %u: %d\n", (unsigned int)p->tid, p->val); } void * tf10(void * arg) { pthread_rwlock_t q_rwlock; long long count; /* count of jobs */ Job p,q; Job task = NULL; task = (struct job *)malloc(sizeof(struct job)); /* 设置头结点,该结点不 存储有效信息 */ task->next = NULL; task->val = 0; task->tid = -1; pthread_t tid; struct timeval endtime; float elapsed_time; tid = pthread_self(); /* 得到线程ID */ count = 0; while(count < 100000000){ if(pthread_rwlock_tryrdlock(&q_rwlock) == 0){ /* 锁住队列,使用的是读锁 */ q = arg; p = q->next; while(p != NULL){ /* 遍历队列,寻找属于该线程的作业结构 */ if(tid == p->tid){ count++; } p = p->next; } pthread_rwlock_unlock(&q_rwlock); /* 释放读锁 */ } } gettimeofday(&endtime, NULL); /* 得到线程结束运行的时间 */ elapsed_time = 1000000 * (endtime.tv_sec-begintime.tv_sec) + endtime.tv_usec -begintime.tv_usec; elapsed_time /= 1000000; /* 计算执行的时间 */ printf("This total used time is: %f seconds.\n", elapsed_time); /* 输出执行时间用于比较 */ return (void *)0; } int main(void) { Job item; pthread_t tid1, tid2; pthread_rwlock_t q_rwlock; int i,err; pthread_rwlock_init(&q_rwlock, NULL); /* 初始化读写锁 */ gettimeofday(&begintime, NULL); /* 得到程序起始的运行时间 */ item = (Job)malloc(sizeof(struct job)); /* 初始化链表的头结点 */ item->next = NULL; item->val = 0; item->tid = -1; if((err=pthread_create(&tid1, NULL, tf10, item)) == -1){ /* 创建第1个线程 */ printf("fail to create thread %s\n", strerror(err)); exit(0); } if((err=pthread_create(&tid2, NULL, tf10, item)) == -1){ /* 创建第2个线程 */ printf("fail to create thread %s\n", strerror(err)); exit(0); } printf("===the 1st put===\n"); pthread_rwlock_wrlock(&q_rwlock); /* 锁住链表,向其中插入五个作业结构,使用 的是写锁 */ for(i = 0; i < 2; i++){ if(insert(item, i, tid1) == -1) exit(1); if(insert(item, i + 1, tid2) == -1) exit(1); } if(insert(item, 10, tid1) == -1) /* 再次插入一个结点 */ exit(1); pthread_rwlock_unlock(&q_rwlock); /* 释放写锁 */ sleep(1); /* 休眠一秒钟,等待两个线程运行 */ printf("===the 2nd put===\n"); pthread_rwlock_wrlock(&q_rwlock); /* 再次锁住作业结构队列,使用写锁 */ if(insert(item, 9, tid2) == -1) exit(1); pthread_rwlock_unlock(&q_rwlock); /* 释放写锁 */ err = pthread_join(tid1, NULL); /* 得到线程1的结束信息 */ if(err != 0){ printf("can’t join thread %s\n", strerror(err)); exit(1); } pthread_join(tid2, NULL); /* 得到线程2的结束信息 */ if(err != 0){ printf("can’t join thread %s\n", strerror(err)); exit(1); } printf("main thread done\n"); pthread_rwlock_destroy(&q_rwlock); /* 销毁读写锁 */ return 0; }
互斥量的使用掌握之后对于读写锁就很好理解了,主要掌握的就是读与写的区别。