Linux的线程锁【转】

久未见 提交于 2020-05-08 15:31:28

(转自:https://blog.csdn.net/u010304442/article/details/90449716

1.互斥锁
在线程实际运行过程中,我们经常需要多个线程保持同步。这时可以用互斥锁来完成任务。
1.1锁的创建
互斥锁可以动态或静态的被创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;



动态创建是通过pthread_mutex_init函数实现,函数原型如下:

int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t * attr);
其中:
mutex:所需创建的锁;
attr:创建锁的属性。一般默认为NULL,分为以下几个属性:
    * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后
             按优先级获得锁。这种锁策略保证了资源分配的公平性;
    * PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求
            ,则在加锁线程解锁时重新竞争;
    * PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP
             类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁;
    * PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争;    


函数成功执行后,互斥锁被初始化为锁住态。

1.2锁操作
对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,函数原型如下:

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而不是挂起等待。

1.3锁销毁
创建的互斥锁在不使用的时候需要消耗,不然会造成系统资源的流失,其函数原型如下:

int pthread_mutexattr_destroy (pthread_mutex_t *mutex);


1.4示例代码

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;

void* consume(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        printf("************************consume begin lock\n");  
        printf("************************consumed %d\n",count);  
        count++;
        sleep(2);
        printf("************************consume over lock\n"); 
        pthread_mutex_unlock(&mutex); 
        printf("************************I'm out of pthread_mutex\n"); 
        sleep(1);
    }  
    return NULL;
}

void* produce( void * arg )
{
    while(1)
    {
        pthread_mutex_lock(&mutex );
        printf("product begin lock\n");
        printf("produced %d\n", count);
        printf("product over lock\n");
        pthread_mutex_unlock(&mutex );
        printf("I'm out of pthread_mutex\n");
        sleep(1);
    }    
    return NULL;
}

int main( void )
{
    pthread_t thread1,thread2;
    pthread_create(&thread1, NULL, &produce, NULL );
    pthread_create(&thread2, NULL, &consume, NULL );
    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;
}


2.读写锁
读写锁: 我们在编写多线程的时候,我们可能需要经常去读取某个共享数据变量,但是相对要改写这个变量的机会相对较少。在读的过程中,往往伴随着查找的操作,中间耗时很长,给这种代码加锁,会极大的降低我们程序的效率。所以提出了读写锁。
读写锁具有写独占,读共享,写锁优先级高的特性。


2.1.锁的初始化
读写锁可以静态或动态的获取,其中静态获取的方式如下:

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;
其中 PTHREAD_RWLOCK_INITIALIZER 是 “pthread_rwlock.h” 中一段宏定义


动态获取的函数原型如下:

int pthread_rwlock_init(pthread_rwlock_t *rw, pthread_rwlockattr_t *attr);
其中:
rw:读写锁;
attr:设置锁的属性,如下:
        PTHREAD_PROCESS_SHARED:允许可访问用于分配读写锁的内存的任何线程对读写锁进行处理。即使该锁是在由多个进程共享的内存
                                中分配的,也允许对其进行处理;
        PTHREAD_PROCESS_PRIVATE:读写锁只能由某些线程处理,这些线程与初始化该锁的线程在同一进程中创建。如果不同进程的线程
                                尝试对此类读写锁进行处理,则其行为是不确定的。缺省值为 PTHREAD_PROCESS_PRIVATE;


返回值:成功返回0,错误返回具体错误代码。

2.2锁的获取与释放
相关函数原型如下:

获取一个读出锁,如果对应的读写锁已由某个写入者持有,那就阻塞调用线程:
int pthread_rwlock_rdlock(pthread_rwlock_t *);

获取一个写入锁,如果对应的读写锁已由另一个写入者持有,或者已由一个或多个读出者持有,那就阻塞调用线程:
int pthread_rwlock_wrlock(pthread_rwlock_t *);

释放一个读出锁或写入锁:
int pthread_rwlock_unlock(pthread_rwlock_t *);

下面两个函数尝试获取一个读出锁或写入锁,但是如果该锁不能马上取得,那就返回一个EBUSY错误,而不是把调用线程投入睡眠:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int pthread_rwlock_trywrlock(pthread_rwlock_t *);


返回值:成功返回0,错误返回具体错误代码。

2.3锁的销毁
函数原型如下:

int pthread_rwlock_destroy(pthread_rwlock_t *);


返回值:成功返回0,错误返回具体错误代码。

2.4示例代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <bits/pthreadtypes.h>
 
#define  WORK_SIZE  1024
 
static  pthread_rwlock_t  rwlock;
char work_area[WORK_SIZE];
int time_to_exit;
 
void *thread_to_read_one(void *arg);
void *thread_to_read_two(void *arg);
void *thread_to_write_one(void *arg);
void *thread_to_write_two(void *arg);
 
void *thread_to_read_one(void *arg)
{
    printf("thread read one try to get lock\n"); 
    pthread_rwlock_rdlock(&rwlock);
    while(strncmp("end",work_area,3) != 0){
        printf("this is thread read one\n");
        printf("the characters is %s\n",work_area);
        pthread_rwlock_unlock(&rwlock);
        sleep(2);
        pthread_rwlock_rdlock(&rwlock);
        while(work_area[0] == '\0'){
           pthread_rwlock_unlock(&rwlock);
           sleep(2);
           pthread_rwlock_rdlock(&rwlock);
        }
    }
    pthread_rwlock_unlock(&rwlock);
    time_to_exit = 1;
    pthread_exit(0);
}
 
void *thread_to_read_two(void *arg)
{
    printf("thread read one try to get lock\n");
    pthread_rwlock_rdlock(&rwlock);
    while(strncmp("end",work_area,3) != 0){
        printf("this is thread read two\n");
        printf("the characters is %s\n",work_area);
        pthread_rwlock_unlock(&rwlock);
        sleep(5);
        pthread_rwlock_rdlock(&rwlock);
        while(work_area[0] == '\0'){
             pthread_rwlock_unlock(&rwlock);
             sleep(5);
             pthread_rwlock_rdlock(&rwlock);
        }
    }
    pthread_rwlock_unlock(&rwlock);
    time_to_exit = 1;
    pthread_exit(0);
}

void *thread_to_write_one(void *arg)
{
    printf("this is write thread one try to get lock\n");
    while(!time_to_exit){
           pthread_rwlock_wrlock(&rwlock);
           printf("this is write thread one\n,input some text,enter 'end'  to finish\n");
           fgets(work_area,WORK_SIZE,stdin);
           pthread_rwlock_unlock(&rwlock);
           sleep(15); // forget sleep,so write always
    }
 
    pthread_rwlock_unlock(&rwlock);
    pthread_exit(0);
}

void *thread_to_write_two(void *arg)
{
    sleep(10);
    while(!time_to_exit){
       pthread_rwlock_wrlock(&rwlock);
       printf("this is write thread two\n input some text,enter 'end' to finish\n");
       fgets(work_area,WORK_SIZE,stdin);
       pthread_rwlock_unlock(&rwlock);
       sleep(20);
    }
    pthread_rwlock_unlock(&rwlock);
    pthread_exit(0);
}
 
int main(int argc,char *argv[])
{
    int retval;
    pthread_t a_thread,b_thread,c_thread,d_thread;
    void *thread_result;
 
    retval = pthread_rwlock_init(&rwlock,NULL);
    if(retval != 0){
          exit(1);
    }
 
    retval = pthread_create(&a_thread,NULL,thread_to_read_one,NULL);
    if(retval != 0){
          exit(1);
    }
 
    retval = pthread_create(&b_thread,NULL,thread_to_read_two,NULL);
    if(retval != 0){
          exit(1);
    }
 
    retval = pthread_create(&c_thread,NULL,thread_to_write_one,NULL);
    if(retval != 0){
          exit(1);
    }
    retval = pthread_create(&d_thread,NULL,thread_to_write_two,NULL);
    if(retval != 0){
          exit(1);
    }
 
    pthread_join(a_thread,NULL);
    pthread_join(b_thread,NULL);
    pthread_join(c_thread,NULL);
    pthread_join(d_thread,NULL);
 
    pthread_rwlock_destroy(&rwlock);
    printf("main thread will exit\n");
    return 0;
}


3.文件锁
文件锁是用于解决资源的共享使用的一种机制:当多个用户需要共享一个文件时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括建议性锁和强制性锁:
建议性锁:要求每个使用上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁,它们依靠程序员遵守这个规定。
强制性锁:是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux中,实现文件上锁的函数有fcntl()和flock(),下面来分别做介绍。





3.1 fcntl实现文件锁
函数原型如下:

int fcntl(int fd, int cmd, struct flock *lock);
其中:
fd:文件描述符
cmd:可选参数如下:
        F_DUPFD:复制一个现存的描述符
        F_GETFD:获得fd的close-on-exec(执行时关闭)文件描述符标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态
        F_SETFD:设置close-on-exec 标志,该标志由参数arg 的FD_CLOEXEC位决定
        F_GETFL:得到open设置的标志
        F_SETFL :改变open设置的标志
        F_GETLK:根据lock参数值,决定是否可以上文件锁
        F_SETLK:设置lock参数值的文件锁

flock :数据结构如下:
        struct flock {
            short l_type;    /* 锁类型: F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK (删除锁)*/
            short l_whence;  /* SEEK_SET(起始),SEEK_CUR(当前), SEEK_END(结尾) */
            off_t l_start;   /* 根据l_whence决定偏移量,可以为负数 */
            off_t l_len;     /* 锁定字节数,0 代表直到EOF */
            pid_t l_pid;     /* 阻止我们取得锁的pid (F_GETLK only) */
        };


示例代码如下:

int fd = open(“./test.txt”, O_RDWR | O_CREAT, LOCKMODE);
struct flock fl; 
fl.l_type = F_WRLCK;    /* write lock */
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;           //lock the whole file
fl.l_pid = getpid();
fcntl(fd, F_SETLK, &fl);


3.2 flock实现文件锁
函数原型如下:

int flock(int fd, int operation);
其中:
fd:打开的文件描述符;
operation:参数可选值如下:
            LOCK_SH:放置共享锁;
            LOCK_EX: 放置互斥锁;
            LOCK_UN:解锁;
            LOCK_NB:非阻塞锁请求,可如上述三个参数联合使用;


在默认情况下,如果另一进程已经持有了文件上的一个不兼容的锁,那么flock()会阻塞,如果设置成非阻塞模式,那么将返回-1,errno设置成 EWOULDBLOCK。

任意数量的进程可以持有文件的共享锁,但在同一时刻只能有一个进程拥有互斥锁,一旦一个进程拥有互斥锁,其他进程的共享锁请求也会被拒绝。

示例代码如下:

#include <time.h>
#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>

#define BUF_SIZE 1000

char *currTime(const char *fmt);

void errExit(char *msg)
{
    perror(msg);
    exit(1);
}

char * currTime(const char *format)
{
    static char buf[BUF_SIZE];
    time_t t;
    size_t s;
    struct tm *tm;

    t = time(NULL);
    tm = localtime(&t);
    if (tm == NULL)
        return NULL;

    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

    return (s == 0) ? NULL : buf;
}

int main(int argc, char *argv[])
{
    int fd, lock;
    const char *lname;

    if (argc < 3 || strcmp(argv[1], "--help") == 0 || strchr("sx", argv[2][0]) == NULL){
        printf("%s file lock [sleep-time]\n"
                 "    'lock' is 's' (shared) or 'x' (exclusive)\n"
                 "        optionally followed by 'n' (nonblocking)\n"
                 "    'sleep-time' specifies time to hold lock\n", argv[0]);
        return 0;
    }

    lock = (argv[2][0] == 's') ? LOCK_SH : LOCK_EX;
    if (argv[2][1] == 'n')
        lock |= LOCK_NB;

    fd = open(argv[1], O_RDONLY);               /* Open file to be locked */
    if (fd == -1)
        errExit("open");

    lname = (lock & LOCK_SH) ? "LOCK_SH" : "LOCK_EX";

    printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname,
            currTime("%T"));

    if (flock(fd, lock) == -1) {
        if (errno == EWOULDBLOCK){
            printf("PID %ld: already locked - bye!", (long) getpid());
            return 1;
        }else{
            printf("flock (PID=%ld)", (long) getpid());
            return 1;
        }
    }

    printf("PID %ld: granted    %s at %s\n", (long) getpid(), lname, currTime("%T"));

    sleep((argc > 3) ? atoi(argv[3]) : 10);

    printf("PID %ld: releasing  %s at %s\n", (long) getpid(), lname, currTime("%T"));
    if (flock(fd, LOCK_UN) == -1)
        errExit("flock");

    exit(EXIT_SUCCESS);
}


4.自旋锁
自旋锁是一种特殊的互斥锁,当资源被枷锁后,其他线程想要再次加锁,此时该线程不会被阻塞睡眠而是陷入循环等待状态(不能在做其它事情),循环检查资源持有者是否已经释放了资源,这样做的好处是减少了线程从睡眠到唤醒的资源消耗,但会一直占用CPU的资源。适用于资源的锁被持有的时间短,而又不希望在线程的唤醒上花费太多资源的情况。

 


————————————————
版权声明:本文为CSDN博主「浪里个浪の」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010304442/article/details/90449716


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!