unix系统编程day07--线程同步,进程同步

同步概念

线程同步

  • 理解:多个线程之间有序的执行。

数据混乱原因

  1. 资源共享(独享资源不会)
  2. 调度随机(意味着数据访问出现竞争)
  3. 缺乏合理的线程调度机制

互斥量mutex

pthread_mutex_t

解释:可以简单认为是一把锁,拥有0和1两个值,0为加锁状态,1为解锁状态

主要应用函数

pthread_mutex_init

  • 函数原型:int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr)
  • 作用:初始化一个锁
  • 返回值:成功返回0,失败返回错误号

pthread_mutex_destroy

  • 函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex)
  • 作用:销毁一个锁
  • 返回值:成功返回0,失败返回错误号

pthread_mutex_lock

  • 函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex)
  • 作用:给一个锁上锁
  • 返回值:成功返回0,失败返回错误号

pthread_mutex_unlock

  • 函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex)
  • 作用:给一个锁解锁
  • 返回值:成功返回0,失败返回错误号

pthread_mutex_trylock

  • 函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • 作用:查看一个锁是否为上锁状态
  • 返回值:未上锁返回0,否则返回错误号

死锁

两种造成死锁的方式

  1. 连续两次调用pthread_mutex_lock函数导致死锁,程序阻塞
  2. 线程A拥有A锁,请求B锁,但是线程B拥有B锁,请求A锁。

第二种死锁代码演示

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

pthread_mutex_t mutex1, mutex2;

void *t_fun(void * arg) {
    while(1) {
        pthread_mutex_lock(&mutex1);
        printf("hello ");
        sleep(2);
        pthread_mutex_lock(&mutex2);
        printf("world\n");
        sleep(1);
        pthread_mutex_unlock(&mutex1);
        pthread_mutex_unlock(&mutex2);
    }
}

int main(void) {
    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_create(&tid, &attr, t_fun, NULL);
    sleep(1);
    while(1) {
        pthread_mutex_lock(&mutex2);
        printf("World\n");
        sleep(1);
        pthread_mutex_lock(&mutex1);
        printf("Hello ");
    }
    pthread_attr_destroy(&attr);
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
    return 0;
}

如何解决死锁

  1. 加锁之前检查是否解锁
  2. 如果trylock失败,那么释放自己拥有的锁

读写锁

读写锁特性

写独占,读共享,写锁优先级高。
写锁独占,读锁可以共享,如果读写锁同时出现,写锁的优先级高于读锁

相关函数

pthread_rwlock_init

  • 具体参见manpages

pthread_rwlock_destroy

  • 具体参见manpages

pthread_rwlock_rdlock

  • 具体参见manpages

pthread_rwlock_wrlock

  • 具体参见manpages

pthread_rwlock_unlock

  • 具体参见manpages

pthread_rwlock_tryrdlock

  • 具体参见manpages

pthread_rwlock_trywrlock

  • 具体参见manpages

以上函数仿照mutex函数,并且成功返回0,失败返回错误号

条件变量

特性:条件变量本身不是锁!但它可以造成线程阻塞。通常和互斥锁配合,给多线程提供一个回合的场所。

条件变量

相关函数

pthread_cond_init

  • 具体参见manpages

pthread_cond_destroy

  • 具体参见manpages

pthread_cond_wait

  • 函数原型int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • 函数作用:
    1. 阻塞线程并等待cond条件满足,又带你想sigsuspend函数
    2. 解锁mutex
    3. 该函数被唤醒的时候返回,解除线程的阻塞,并重新获得mutex
      1.2.为原子操作,也就是说在调用该函数的时候两个操作同时完成
  • 返回值:成功返回0,失败返回错误号

pthread_cond_timedwait

  • 相对于_wait函数,timedwait多了最后一个参数
  • timespec *abstime结构体中有两个变量,一个是秒,一个是纳秒,这个时间是相对于Liunx元年(1970/1/1 0:0:0)来定义的,所以如果需要定时需要首先获取当前时间,然后+?(来设置多少秒的定时)
time_t cur = time(NULL);
timespec t = {cur + 1, 0);//用来定时1s
pthread_cond_timedwait(&cond, &mutex, &t);

pthread_cond_signal

  • 函数原型:int pthread_cond_signal(pthread_cond_t *cond);
  • 函数作用:解除通过条件cond阻塞的线程
  • 返回值:成功返回0,失败返回错误号

pthread_cond_broadcast

  • 函数作用:解除通过条件cond阻塞的线程
  • 返回值:成功返回0,失败返回错误号

生产者消费者模型

模型简介

该模型为存在一个共享数据区,消费者去消耗共享数据区的内容,生产者去生产共享数据区的数据,生产者和消费者的线程之间要相互配合,通过条件变量实现线程同步。

代码实现模型

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

struct Message {
    Message * nex;
    int num;
};
Message *head, *tail;
pthread_mutex_t lock       = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  has_product = PTHREAD_COND_INITIALIZER;

void * productor(void * arg) {
    while(1) {
        tail = (Message *)malloc(sizeof(Message));
        tail->num = rand() % 1000 + 1;
        printf("product num = %d\n", tail->num);
        pthread_mutex_lock(&lock);
        tail->nex = head;
        head = tail;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);
        sleep(rand() % 5);
    }
    return NULL;
}

void * consumer(void * arg) {
    while(1) {
        pthread_mutex_lock(&lock);
        while(head == NULL) {
            pthread_cond_wait(&has_product, &lock);
        }
        Message * mp = head;
        head = head->nex;
        pthread_mutex_unlock(&lock);
        printf("Eat num = %d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }
    return NULL;
}


int main(void) {
    pthread_t pid, cid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&pid, &attr, productor, NULL);
    pthread_create(&cid, &attr, consumer, NULL);
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}

信号量

强化版的互斥量,互斥量我们可以看作是0或1(我们只是这么认为,但是互斥量仍然是结构体
信号量:sem_t 类型

常用函数

sem_init函数

  • 函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 参数解释:第二个参数为0是线程间共享,为1是进程间共享,第三个参数为信号量
  • 返回值成功返回0,失败返回-1

sem_destroy函数

sem_wait函数

  • 相当于lock

sem_post函数

  • 相当于unlock

sem_trywait函数

  • 相当于trylock

sem_timedwait函数

使用信号量实现生产者消费者模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define NUM 5

sem_t sem_product, sem_blank;
int a[NUM];

void * fun_consumer(void * arg) {
    int i = 0;
    while(1) {
        sem_wait(&sem_blank);
        a[i] = rand() % 1000;
        printf("product ------%d\n", a[i]);
        sem_post(&sem_product);
        i = (i + 1) % NUM;
        sleep(rand() % 3);
    }
    return NULL;
}

void * fun_producter(void * arg) {
    int i = 0;
    while(1) {
        sem_wait(&sem_product);
        printf("consumer ----- %d\n", a[i]);
        sem_post(&sem_blank);
        i = (i + 1) % NUM;
        sleep(rand() % 3);
    }
    return NULL;
}


int main(void) {
    pthread_t tid[2];
    sem_init(&sem_product, 0, 0);
    sem_init(&sem_blank, 0, NUM);

    pthread_create(&tid[0], NULL, fun_producter, NULL);
    pthread_create(&tid[1], NULL, fun_consumer, NULL);

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);

    sem_destroy(&sem_product);
    sem_destroy(&sem_blank);

    return 0;
}

进程间互斥量

我们知道互斥量在线程中可以灵活的使用,但是互斥量在进程中也可以做到共享

代码演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
struct Message {
    int num;
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutex_attr;
};

int main(void) {
    srand(time(NULL));
    Message * msg;
    msg = (Message * )mmap(NULL, sizeof(Message), PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANON, -1, 0);
    memset(msg, 0, sizeof(*msg));
    pthread_mutexattr_init(&msg->mutex_attr);
    pthread_mutexattr_setpshared(&msg->mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&msg->mutex, &msg->mutex_attr);
    pid_t pid;
    pid = fork();
    if(pid == 0) {
        while(1) {
            pthread_mutex_lock(&msg->mutex);
            msg->num++;
            printf("child pthread -------- +1 %d\n", msg->num);
            pthread_mutex_unlock(&msg->mutex);
            sleep(rand() % 3);
        }
    } else if(pid > 0) {
        while(1) {
            pthread_mutex_lock(&msg->mutex);
            msg->num += 2;
            printf("------------ parent += 2  %d\n", msg->num);
            pthread_mutex_unlock(&msg->mutex);
            sleep(rand() % 3);
        }
    }
    return 0;
}

文件锁

文件锁用于进程间通讯使用,类似于线程之间的读写锁,但是使用fcntl函数实现

fcntl函数

  • 函数原型:int fcntl(int fildes, int cmd, ...);

副参cmd在文件锁中的应用

在文件锁中用需要用到三个cmd

  1. F_SETLCK:参数为flock结构体,加锁
  2. F_SETLCKW:参数为flock结构体,加锁
  3. F_UNLOCK:解锁

⚠️需要注意的是:文件锁中加锁和解锁需要的是同一个函数,不同的是flock结构体中的东西。

flock结构体

struct flock {
    off_t       l_start;    /* starting offset */
    off_t       l_len;      /* len = 0 means until end of file */
    pid_t       l_pid;      /* lock owner */
    short       l_type;     /* lock type: read/write, etc. */
    short       l_whence;   /* type of l_start */
};

l_type:三种类型,分别为 F_WRLCK(写锁),F_RDLCK(读锁),F_UNLCK(解锁)
l_whence:其实位置
l_start:偏移量
l_len:加锁的长度,如果为0操作的是整个文件
l_pid:拥有这个flock的进程id

⚠️注意:文件锁只能用在进程中,并不能用在线程中,因为各个线程共享文件描述符,同样共享了文件结构体,fcntl又是通过文件描述符操作的文件结构体,所以在线程中不能使用。

使用fcntl加锁解锁文件演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
void sys_err(const char * msg){
    perror(msg);
    exit(1);
}

int main(int argc, char ** argv) {
    if(argc != 2) {
        puts("./file_lock filename");
        exit(1);
    }
    if(access(argv[1], F_OK) < 0) {
        sys_err(const_cast<char *>("file not exist"));
    }
    int fd;
    if((fd = open(argv[1], O_RDWR)) < 0) {
        sys_err(const_cast<char *>("open failed"));
    }
    struct flock f_lock;
    memset(&f_lock, 0, sizeof(f_lock));
    //f_lock.l_type = F_RDLCK;        /* 可选读锁 */
     f_lock.l_type = F_WRLCK;     /* 可选写锁 */
    f_lock.l_whence = SEEK_SET;
    f_lock.l_start = 0;
    f_lock.l_len = 0;

    fcntl(fd, F_SETLKW, &f_lock);

    printf("GET LOCK!!!\n");
    sleep(10);
    
    f_lock.l_type = F_UNLCK;

    fcntl(fd, F_SETLKW, &f_lock);
    printf("LOSE LOCK!\n");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值