天天看点

Linux 学习笔记—线程同步之读写锁、自旋锁、屏障

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;
}
           
Linux 学习笔记—线程同步之读写锁、自旋锁、屏障

继续阅读