3.2.1 读写锁
读写锁和互斥体类似,不过读写锁有更高的并行性,互斥体要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。而读写锁可以有3个状态,读模式下锁住状态,写模式下锁住状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。读写锁适合对数据结构读的次数远大于写的情况。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放它们的读锁为止。
#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockaddr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
// 成功返回0,否则返回错误码
通过pthread_rwlock_init初始化读写锁,如果希望读写锁有默认属性,可以传一个NULL指针给attr。当不再需要读写锁时,调用pthread_rwlock_destroy做清理工作。
#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);
// 成功返回0,否则返回错误码
实例1 读写锁
/**
* 两个读线程读取数据,一个写线程更新数据
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define READ_THREAD 0
#define WRITE_THREAD 1
int g_data = 0;
pthread_rwlock_t g_rwlock;
void *func(void *pdata)
{
int data = (int)pdata;
printf("data=%d\n",data);
while (1) {
if (READ_THREAD == data) {
pthread_rwlock_rdlock(&g_rwlock);
printf("-----%d------ %d\n", pthread_self(), g_data);
sleep(1);
pthread_rwlock_unlock(&g_rwlock);
sleep(1);
}
else {
pthread_rwlock_wrlock(&g_rwlock);
g_data++;
printf("g_data=%d\n",g_data);
printf("add the g_data\n");
pthread_rwlock_unlock(&g_rwlock);
sleep(1);
}
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t1, t2, t3;
pthread_rwlock_init(&g_rwlock, NULL);
pthread_create(&t1, NULL, func, (void *)READ_THREAD);//0
pthread_create(&t2, NULL, func, (void *)READ_THREAD);//0
pthread_create(&t3, NULL, func, (void *)WRITE_THREAD);//1
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_rwlock_destroy(&g_rwlock);
return 0;
}
3.2.2 自旋锁(汽车等红绿灯不熄火)
自旋锁和互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)状态,自旋锁可用于下面的情况:锁被持有的时间短,并且线程不希望再重新调度上花费太多的成本。自旋锁通常作为底层原语用于实现其他类型的锁。根据他们所基于的系统架构,可以通过使用测试并设置指令有效地实现。当然这里说的有效也还是会导致CPU资源的浪费:当线程自旋锁变为可用时,CPU不能做其他任何事情,这也是自旋锁只能够被只有一小段时间的原因。
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
pshared参数表示进程共享属性,表明自旋锁是如何获取的,如果它设为PTHREAD_PROCESS_SHARED,则自旋锁能被可以访问锁底层内存的线程所获取,即使那些线程属于不同的进程。否则pshared参数设为PTHREAD_PROCESS_PROVATE,自旋锁就只能被初始化该锁的进程内部的线程访问到。
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
如果自旋锁当前在解锁状态,pthread_spin_lock函数不要自旋就可以对它加锁,试图对没有加锁的自旋锁进行解锁,结果是未定义的。需要注意,不要在持有自旋锁情况下可能会进入休眠状态的函数,如果调用了这些函数,会浪费CPU资源,其他线程需要获取自旋锁需要等待的时间更长了。
实例2 自旋锁
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_spinlock_t g_lock;
int g_data = 0;
void *func(void *arg)
{
while (1) {
pthread_spin_lock(&g_lock);
g_data++;
printf("----------- %d\n", g_data);
sleep(1);
pthread_spin_unlock(&g_lock);
}
}
int main(int argc, char **argv)
{
pthread_t tid;
pthread_spin_init(&g_lock, PTHREAD_PROCESS_PRIVATE);
pthread_create(&tid, NULL, func, NULL);
pthread_create(&tid, NULL, func, NULL);
pthread_create(&tid, NULL, func, NULL);
pthread_join(tid, NULL);
return 0;
}
3.2.3 屏障(了解)
屏障是用户协调多个线程并行工作的同步机制,屏障允许每个线程等待,直到所有合作的线程都到达某一点,然后从该点出继续执行。pthread_join其实就是一种屏障,允许一个线程等待,直到另一个线程退出。但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,所有线程达到屏障后可以继续工作。
#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
// 成功返回0,否则返回错误编号
初始化屏障时,可以使用count参数指定,在允许所有线程继续运行前,必须达到屏障的线程数目。attr指定屏障属性,NULL为默认属性。
#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
// 成功返回0,否则返回错误编号
可以使用pthread_barrier_wait函数来表明,线程已完成工作,准备等所有其他线程赶过来。调用pthread_barrier_wait的线程在屏障计数未满足条件时,会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程,则所有的线程会被唤醒。
一旦到达屏障计数值,而且线程处于非阻塞状态,屏障就可以被重复使用。
实例3 屏障
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_barrier_t g_barrier;
void *func(void *arg)
{
int id = (int )arg;
if (id == 0) {
printf("thread 0\n");
sleep(1);
pthread_barrier_wait(&g_barrier);
printf("thread 0 come...\n");
}
else if (id == 1) {
printf("thread 1\n");
sleep(2);
pthread_barrier_wait(&g_barrier);
printf("thread 1 come...\n");
}
else if (id == 2) {
printf("thread 2\n");
sleep(3);
pthread_barrier_wait(&g_barrier);
printf("thread 2 come...\n");
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t1, t2, t3;
pthread_barrier_init(&g_barrier, NULL, 3);
pthread_create(&t1, NULL, func, (void *)0);
pthread_create(&t2, NULL, func, (void *)1);
pthread_create(&t3, NULL, func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
return 0;
}
补充:管道在多线之间通信实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
Thread *m_Threads;
static int threadcount = 1;
void* work_thread(void* argc)
{
Thread* param = (Thread*) argc;
printf("childthread_tid=%lu\n", param->tid);
int contant = 0;
//sleep(2);
printf("childthread--read return %d\n",read(param->notifyReceiveFd, &contant, sizeof(int)));
printf("childthread--read from pipe %d\n", contant);
}
int main(int argc, char** argv)
{
//在主线程和子线程之间建立管道
m_Threads = malloc(sizeof(Thread) * threadcount);
int fds[2];
if( pipe(fds) )
{
perror("create pipe error");
}
m_Threads[0].notifyReceiveFd = fds[0];
pthread_create(&m_Threads[0].tid, NULL, work_thread, (void*)&m_Threads[0]);
printf("mainthread_tid=%lu\n", m_Threads[0].tid);
int contant = 1;
// sleep(2);
printf("mainthread--write %d to pipe\n", contant);
printf("mainthread--write return %d\n",write(fds[1], &contant, sizeof(int)));
pthread_join(m_Threads[0].tid, NULL);
close(fds[0]);
close(fds[1]);
return 0;
}