【linux】linux下多种锁的比较 和 实现单实例的方法

线程同步的各种方法。包括:

  • 互斥量(mutex)
  • 读写锁
  • 条件变量
  • 信号量
  • 文件互斥

在linux内核中,有很多同步机制。比较经典的有

原子操作

spin_lock(忙等待的锁)

mutex(互斥锁)

semaphore(信号量)

互斥量(mutex)

创建互斥锁

静态方式

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

POSIX定义了一个结构常量宏PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁

动态方式

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
         *mutexattr)
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。

注销互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex)

 

锁操作

int pthread_mutex_lock(pthread_mutex_t *mutex)      //加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)  //解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex)  //测试加锁


pthread_mutex_trylock() 语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回 EBUSY而不是挂起等待。

#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing

pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; // it's mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; //it's mean writer can writing

int readCount = 0;

void *reader(void *arg)
{
    int n = M;
    int id = (int)arg;
    while (n--)
    {
        sleep( rand() % 3);
        pthread_mutex_lock(&rd);
        readCount++;
        if( readCount == 1)
        {
            pthread_mutex_lock(&wr);
        }
        pthread_mutex_unlock(&rd);

        printf("reader %d is reading\n", id);
        sleep( rand() % 3);

        pthread_mutex_lock(&rd);
        readCount--;
        if (readCount == 0)
        {
            pthread_mutex_unlock(&wr);
        }
        pthread_mutex_unlock(&rd);
        printf("reader %d is leaving\n", id);
    }
    printf("----reader %d has done----\n", (int)arg);
}

void *writer(void *arg)
{
    int n = M;
    while (n--)
    {
        sleep( rand() % 3);
        pthread_mutex_lock(&wr);
        printf("\twriter is writing\n");
        sleep( rand() % 3);
        pthread_mutex_unlock(&wr);
        printf("\twriter is leaving\n");
    }
    printf("----writer has done----\n");

}


int main(int argc, const char *argv[])
{
    int err;
    pthread_t tid[N], writerTid;
    int i;


    err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
    if (err != 0)
    {
        err_quit("can't create process for writer");
    }

    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, reader, (void *)(i + 1));
        if (err != 0)
        {
            err_quit("can't create process for reader");
        }
    }
    pause();
    return 0;
}

g++ reader_writer.c -l pthread -o a.out

互斥量实现读者写者问题(写者优先)

#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing
 
pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER;       // it's mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER;       // it's mean writer can writing
pthread_mutex_t priority = PTHREAD_MUTEX_INITIALIZER; // it's mean writer can writing
 
int readCount = 0;
 
void* reader(void *arg)
{
    int n = M;
    int id = (int)arg;
    while (n--)
    {
        sleep( rand() % 3);
 
        pthread_mutex_lock(&priority);
        pthread_mutex_lock(&rd);
        readCount++;
        if( readCount == 1)
        {  //first reader
            pthread_mutex_lock(&wr);
        }
        pthread_mutex_unlock(&rd);
        pthread_mutex_unlock(&priority);
 
        printf("reader %d is reading\n", id);
        sleep( rand() % 3);
 
        pthread_mutex_lock(&rd);
        readCount--;
        if (readCount == 0)
        {  //last reader
            pthread_mutex_unlock(&wr);
        }
        pthread_mutex_unlock(&rd);
        printf("reader %d is leaving\n", id);
    }
    printf("----reader %d has done----\n", (int)arg);
}
 
void* writer(void *arg)
{
    int n = M;
    while (n--)
    {
        sleep( rand() % 4);
        pthread_mutex_lock(&priority);
        pthread_mutex_lock(&wr);
        printf("\twriter is writing\n");
        sleep( rand() % 4);
        pthread_mutex_unlock(&wr);
        pthread_mutex_unlock(&priority);
        printf("\twriter is leaving\n");
    }
    printf("----writer has done----\n");
 
}
 
int main(int argc, const char *argv[])
{
    int err;
    pthread_t tid[N], writer_tid;
    int i;
 
    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
        if (err != 0)
        {
            err_quit("can't create process for reader");
        }
    }
 
    err = pthread_create(&writer_tid, NULL, writer, (void *)NULL);
    if (err != 0)
    {
        err_quit("can't create process for writer");
    }
    pause();
    return 0;
}

 

读写锁

读写锁适合于对数据结构的读次数比写次数多得多的情况.因为,读锁时可以共享,写锁时独占,所以读写锁又叫共享-独占锁.

初始化和销毁

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const
    pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);


成功则返回0,出错则返回错误编号. 同互斥量以上,在释放读写锁占用的内存之前,需要先通过 pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.

读和写

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

成功则返回0,出错则返回错误编号.这3个函数分别实现获取读锁,获取写锁和释放锁的操作.

非阻塞版

获取锁的两个函数是阻塞操作,同样,非阻塞的函数为:

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

成功则返回0,出错则返回错误编号.非阻塞的获取锁操作,如果可以获取则返回0,否则返回 错误的EBUSY.

#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing
 
pthread_rwlock_t lock; //it's mean writer can writing
 
int readCount = 0;
 
void* reader(void *arg)
{
    int n = M;
    int id = (int)arg;
    while (n--)
    {
        sleep( rand() % 3);
        pthread_rwlock_rdlock(&lock);
        printf("reader %d is reading\n", id);
        sleep( rand() % 3);
 
        pthread_rwlock_unlock(&lock);
        printf("reader %d is leaving\n", id);
    }
    printf("----reader %d has done----\n", (int)arg);
}
 
void* writer(void *arg)
{
    int n = M;
    while (n--)
    {
        sleep( rand() % 3);
        pthread_rwlock_wrlock(&lock);
        printf("\twriter is writing\n");
        sleep( rand() % 3);
        pthread_rwlock_unlock(&lock);
        printf("\twriter is leaving\n");
    }
    printf("----writer has done----\n");
}
 
int main(int argc, const char *argv[])
{
    int err;
    pthread_t tid[N], writerTid;
    int i;
 
    err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
    if (err != 0)
    {
        err_quit("can't create process for writer");
 
    }
 
    pthread_rwlock_init(&lock, NULL);
    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
        if (err != 0)
        {
            err_quit("can't create process for reader");
        }
    }
    pause();
    pthread_rwlock_destroy(&lock);
    return 0;
}

 

条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使 "条件成立"(给出条件成立信号)。

条件的检测是在互斥锁的保护下进行的。

如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多 个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。

条件变量分为两部分:条件和变量.

条件本身是由互斥量保护的.线程在改变条件状态前先要锁住 互斥量.它利用线程间共享的全局变量进行同步的一种机制。

相关的函数如下:

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//动态初始化
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const
      timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

简要说明:

初始化:

条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//动态初始化
pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;                     //静态初始化

动态初始化,释放动态条件变量的内存空间之前, 要用 pthread_cond_destroy对其进行清理.

成功则返回0, 出错则返回错误编号.

当pthread_cond_init的attr参数为NULL时,会创建一个默认属性的条件变量;非默认情况以后讨论.

等待条件:

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t
        *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t
        *restrict mutex, const struct timespec *restrict timeout);

成功则返回0,出错则返回错误编号.这两个函数分别是阻塞等待和超时等待.

通知条件

#include <pthread.h> 
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);

成功则返回0, 出错则返回错误编号.

这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
{
 
    pthread_cleanup_push(pthread_mutex_unlock, &mutex);
 
    //提供函数回调保护
    while (1) {
 
        printf("thread1 is running\n");
 
        pthread_mutex_lock(&mutex);
 
        pthread_cond_wait(&cond, &mutex);
 
        printf("thread1 applied the condition\n");
 
        pthread_mutex_unlock(&mutex);
 
        sleep(4);
 
    }
 
    pthread_cleanup_pop(0);
 
}
 
 
void *thread2(void *arg)
{
 
    while (1) {
 
        printf("thread2 is running\n");
 
        pthread_mutex_lock(&mutex);
 
        pthread_cond_wait(&cond, &mutex);
 
        printf("thread2 applied the condition\n");
 
        pthread_mutex_unlock(&mutex);
 
        sleep(1);
 
    }
}
 
 
 
int main()
{
 
    pthread_t thid1, thid2;
 
    printf("condition variable study!\n");
 
    pthread_mutex_init(&mutex, NULL);
 
    pthread_cond_init(&cond, NULL);
 
    pthread_create(&thid1, NULL, (void *) thread1, NULL);
 
    pthread_create(&thid2, NULL, (void *) thread2, NULL);
 
    do {
 
        pthread_cond_signal(&cond);
 
    } while (1);
 
    sleep(20);
 
    pthread_exit(0);
 
    return 0;
 
}

信号量

信号量的具体使用方法,请

man sem_init

相关的几个系统调用:

sem_init
sem_wait
sem_trywait
sem_post
sem_getvalue
sem_destory

下面来看一个信号量解决哲学家就餐问题,在这里semaphore 初始为1,用法和互斥量没有什么区别。

#include <semaphore.h>
#include <pthread.h>
#include "apue.h"
 
#define N 5 // No. of philosopher
#define M 5 // times of eating
sem_t forks[N];
 
void * thr_philosopher( void *arg);
int main(int argc, char* argv[])
{
    int i = 0;
    int err;
    pthread_t tid[N];
    void *tret;
    //initilize semaphore
    for (i = 0; i < N; i++)
    {
        if(sem_init(&forks[i], 0, 1) != 0)
        {
            err_quit("init forks error");
        }
    }
    //create thread
    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, thr_philosopher, (void *)i);
        if (err != 0)
        {
            err_quit("can't create thread %d: %s\n", i + 1, strerror(err));
        }
    }
 
    //get the return value
    for (i = 0; i < N; i++)
    {
        err = pthread_join(tid[i], &tret);
        if (err != 0)
        {
            err_quit("can't join with philosopher %d : %s\n", i + 1,
                    strerror(err));
        }
        printf("-------------------philosopher %d has done-------------------\n", (int)tret);
    }
 
    // delete the source of semaphore
    for (i = 0; i < N; i++)
    {
        err = sem_destroy(&forks[i]);
        if (err != 0)
        {
            err_sys("can't destory semaphore");
        }
    }
    exit(0);
}
 
void * thr_philosopher( void *arg)
{
 
    /*
     * here cann't judge arg == NULL
     * because (void *)0 will lead to arg = NULL
     */
    int n = M;
    int i = 0;
    i = (int)arg;
    while ( n-- )
    {
        sleep(1);
        if ( i == N - 1)
        {
            sem_wait(&forks[0]);
            sem_wait(&forks[i]);
        }
        else
        {
            sem_wait(&forks[i]);
            sem_wait(&forks[i + 1]);
        }
        printf("philosopher %d is eating\n", i + 1);
        if ( i == N - 1)
        {
            sem_post(&forks[0]);
            sem_post(&forks[i]);
        }
        else
        {
            sem_post(&forks[i]);
            sem_post(&forks[i + 1]);
        }
 
    }
 
    return ((void*)i);
}

文件互斥


文件互斥并不是操作系统提供的一组API,而是使用了一点小技巧,我们可以通过linux下文件互斥地打开,实现线程/进程互斥的访问资源,以此实现多线程编程。

值得注意的是,文件互斥的方式不但适用于多线程编程,还能实现多进程之间的交互。

文件互斥还有一个妙用--保证一个系统中只有一个实例,请参考这里。

新建lock.h 文件,内容入下:

#include    <sys/types.h>
#include    <sys/stat.h>
#include    <fcntl.h>
#include    <unistd.h>
void initlock(const char *lockfile);
void lock(const char *lockfile);
void unlock(const char *lockfile);

新建lock.c 文件,内容入下: 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
void initlock(const char *lockfile)
{
    int i;
    unlink(lockfile);
}
 
void lock(const char *lockfile)
{
 
    int fd;
    while ( (fd = open(lockfile, O_RDONLY | O_CREAT | O_EXCL)) < 0)
        sleep(1);
    close(fd);
}
 
void unlock(const char *lockfile)
{
    unlink(lockfile);
}

新建main.c 文件,内容如下:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
#include "lock.h"
 
#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};
 
void takeFork( int i )
{
    if ( i == N - 1 )
    {
        lock(forks[0]);
        printf("philosopher %d:  takefork 0\n", i + 1 );
        lock(forks[i]);
        printf("philosopher %d:  takefork %d\n", i + 1, i );
    }
    else
    {
        lock(forks[i]);
        printf("philosopher %d:  takefork %d\n", i + 1, i );
        lock(forks[i+1]);
        printf("philosopher %d:  takefork %d\n", i + 1, i+1 );
    }
}
 
void eating(int i, int nsecs)
{
    printf("\tphilosopher %d:  eat %d s\n", i + 1, nsecs );
    sleep(nsecs);
}
 
void thinking(int i, int nsecs)
{
    printf("philosopher %d:  think %d sec\n", i + 1, nsecs );
    sleep(nsecs);
}
 
void putFork( int i )
{
    if ( i == N - 1 )
    {
        unlock(forks[0]);
        unlock(forks[i]);
    }
    else
    {
        unlock(forks[i]);
        unlock(forks[i+1]);
    }
}
 
void* philosopher(void *arg)
{
    int i = (int)arg;
//    printf("philosopher %d : pid = %d is running", i + 1, getpid());
    int m = M;
    int nsecs;
    srand( time(0) );
    while ( m-- )
    {
        nsecs = rand() % 5;
        thinking(i, nsecs);
        takeFork(i);
        nsecs = rand() % 5;
        eating(i, nsecs);
        putFork(i);
    }
    printf("====================philosopher %d : is\
 successful===================\n", i + 1 );
}
 
int main(int argc, char* argv[])
{
 
    int i;
    int err;
    pthread_t tid[N];
 
    for (i = 0; i < sizeof(forks) / sizeof(forks[0]); i++)
    {
        initlock(forks[i]);
    }
 
    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, philosopher, (void *)i);
        if (err != 0)
        {
            err_quit("can't create process for philosopher");
        }
    }
    pause();
}

 最后,我们来看一个使用文件互斥实现进程之间的交互的例子,其中,lock.h 和 lock.c 请参考上面的实现。main.c 的实现如下:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
#include "lock.h"
 
#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};
 
void takeFork( int i )
{
    if ( i == N - 1 )
    {
        lock(forks[0]);
        printf("philosopher %d:  takefork 0\n", i + 1 );
        lock(forks[i]);
        printf("philosopher %d:  takefork %d\n", i + 1, i );
    }
    else
    {
        lock(forks[i]);
        printf("philosopher %d:  takefork %d\n", i + 1, i );
        lock(forks[i+1]);
        printf("philosopher %d:  takefork %d\n", i + 1, i+1 );
    }
}
 
void eating(int i, int nsecs)
{
    printf("\tphilosopher %d:  eat %d s\n", i + 1, nsecs );
    sleep(nsecs);
}
 
void thinking(int i, int nsecs)
{
    printf("philosopher %d:  think %d sec\n", i + 1, nsecs );
    sleep(nsecs);
}
 
void putFork( int i )
{
    if ( i == N - 1 )
    {
        unlock(forks[0]);
        unlock(forks[i]);
    }
    else
    {
        unlock(forks[i]);
        unlock(forks[i+1]);
    }
}
 
void* philosopher(void *arg)
{
    int i = (int)arg;
//    printf("philosopher %d : pid = %d is running", i + 1, getpid());
    int m = M;
    int nsecs;
    srand( time(0) );
    while ( m-- )
    {
        nsecs = rand() % 5;
        thinking(i, nsecs);
        takeFork(i);
        nsecs = rand() % 5;
        eating(i, nsecs);
        putFork(i);
    }
    printf("====================philosopher %d : is\
 successful===================\n", i + 1 );
}
 
int main(int argc, char* argv[])
{
 
    int i;
    int err;
    pthread_t tid[N];
 
    for (i = 0; i < sizeof(forks) / sizeof(forks[0]); i++)
    {
        initlock(forks[i]);
    }
 
    for (i = 0; i < N; i++)
    {
        err = pthread_create(&tid[i], NULL, philosopher, (void *)i);
        if (err != 0)
        {
            err_quit("can't create process for philosopher");
        }
    }
    pause();
}

如果你是新手,可能对下面这几行代码不是很理解,请参考酷壳网的《一个fork的面试题》。

for ( i = 0; i < N; i++ )
    {
        pid = fork();
        if (pid == 0 || pid == -1)
        {
            break;
        }
    }

参考:https://blog.csdn.net/KOwzb/article/details/77160249

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值