线程的同步 && 多线程+fork

线程:进程内部的一条执行路径,调度执行的基本单位
进程:一个正在运行的程序,资源分配的基本单位
同步:信号量,互斥锁,读写锁,条件变量
Linux线程的实现,用户,内核,混合
查看线程的ID:ps -L 线程ID

线程的同步

1.信号量

信号量的相关函数

//初始化信号量
 int sem_init(sem_t *sem, int pshared, unsigned int value);
//P
 int sem_wait(sem_t *sem);
//v
int sem_post(sem_t *sem);
//销毁信号量
int sem_destroy(sem_t *sem);

2.互斥锁

互斥锁相关函数

//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁
 int pthread_mutex_unlock(pthread_mutex_t *mutex);
//销毁锁
 int pthread_mutex_destroy(pthread_mutex_t *mutex);

3.读写锁

//初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);
//读锁加锁
 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写锁加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//读写锁销毁
 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

4.条件变量

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程

int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程

int pthread_cond_destroy(pthread_cond_t *cond);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

char buff[128]={0};

void* funa(void* arg)
{
    while(1)
    {
        //等待条件可用
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        pthread_mutex_unlock(&mutex);

        if( strncmp(buff,"end",3) == 0)
        {
            break;
        }
        //打印BUFF
        printf("funa buff = %s\n",buff);
    }
}
void* funb(void* arg)
{
    while(1)
    {
        //等待条件可用
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        pthread_mutex_unlock(&mutex);
        
        if( strncmp(buff,"end",3)==0)
        {
            break;
        }
        printf("funb buff = %s\n",buff);
    }
}

int main()
{
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);

    pthread_t ida,idb;

    pthread_create(&ida,NULL,funa,NULL);
    pthread_create(&idb,NULL,funb,NULL);
    
    while(1)
    {
        //键盘获取数据
        fgets(buff,128,stdin);

        //通知线程
        if( strncmp(buff,"end",3)==0)
        {

            //唤醒所有等待的进程,然后退出
            pthread_mutex_lock(&mutex);
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);
            break;
        }
        else
        {
            //唤醒一个进程
            pthread_mutex_lock(&mutex);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }

    }

    pthread_join(ida,NULL);
    pthread_join(idb,NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}


5.利用信号量和互斥锁实现生产者和消费者模型

//生产者消费者模型
//缓冲区的大小
#define BUFF_MAX 10

//生产者的个数
#define SC_NUM  2
//消费者的个数
#define XF_NUM  3

//缓冲区中存在几个空位
sem_t empty;
//缓冲区中存在几个资源
sem_t full;

pthread_mutex_t mutex;

//缓冲区,同一时刻只允许一个生产者或消费者对缓冲区进行访问
int buff[BUFF_MAX];
//生产者生产一个资源后加1,in = (in+1)%BUFF_MAX
int in=0;
//消费者消费一个资源后加1,out = (out+1)%BUFF_MAX
int out=0;
//生产者线程
void* sc_thread(void* arg)
{
    //此index用来生命传入的值,用以区别
    int index=(int)arg;
    for(int i=0;i<30;i++)
    {
        sem_wait(&empty);
        pthread_mutex_lock(&mutex);
        buff[in] = rand()%100;
        printf("第%d个生产者,下标为%d的生产的%d\n",index,in,buff[in]);
        in = (in +1)%BUFF_MAX;
        pthread_mutex_unlock(&mutex);
        sem_post(&full);
        int n=rand()%10;
        sleep(n);
    }
}

//消费者线程
void* xf_thread(void *arg)
{
    int index=(int)arg;
    for(int i=0;i<20;i++)
    {
        sem_wait(&full);
        pthread_mutex_lock(&mutex);
        printf("第%d个消费者,下标为%d的消费的值为%d\n",index,out,buff[out]);
        out = (out+1)%BUFF_MAX;
        pthread_mutex_unlock(&mutex);
        sem_post(&empty);

        int n=rand()%10;
        sleep(n);
    }
}
int main()
{
    //初始化信号量
    sem_init(&empty,0,BUFF_MAX);
    sem_init(&full,0,0);
    //初始化互斥锁
    pthread_mutex_init(&mutex,NULL);

    srand((int)time(NULL));


    pthread_t sc_id[SC_NUM];
    pthread_t xf_id[XF_NUM];
    for(int i=0;i<SC_NUM;i++)
    {
        //创建生产者线程
        pthread_create(&sc_id[i],NULL,sc_thread,(void*)i);
    }
    for(int i=0;i<XF_NUM;i++)
    {
        //创建消费者线程
        pthread_create(&xf_id[i],NULL,xf_thread,(void*)i);
    }

    for(int i=0;i<SC_NUM;i++)
    {
        pthread_join(sc_id[i],NULL);
    }
    for(int i=0;i<XF_NUM;i++)
    {
        pthread_join(xf_id,NULL);
    }

    //销毁信号量
    sem_destroy(&empty);
    sem_destroy(&full);
    //销毁互斥锁
    pthread_mutex_destroy(&mutex);

    printf("main over\n");

    return 0;
}

6.读写锁的使用

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


//读写锁
//使用场景:少量的写+大量的读
//加锁规则:
//写的情况   一次只有一个线程可以占用写模式的读写锁,一个执行流在进行写的时候,其他执行流既不能写,也不能读,只能陷入阻塞状态
//读的情况:多个读取进程可以同时占用读模式下的读写锁,在读写锁的内部有一个引用计数器
//引用计数:标示者有多少以读模式打开的读写锁线程


//初始化读写锁
pthread_rwlock_t rwlock;

void* read_fun1(void* arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("read1 start\n");
        sleep(1);
        printf("read1 end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}

void* read_fun2(void* arg)
{
    while(1)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("read2 start\n");
        sleep(1);
        printf("read2 end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}

void* write_fun(void* arg)
{
    while(1)
    {
        pthread_rwlock_wrlock(&rwlock);
        printf("write start\n");
        sleep(3);
        printf("write end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
int main()
{
    //初始化读写锁
    pthread_rwlock_init(&rwlock,NULL);
    
    pthread_t idr1,idr2;
    pthread_t idw;
    
    //创建读者线程和写着线程
    pthread_create(&idr1,NULL,read_fun1,NULL);
    pthread_create(&idr2,NULL,read_fun2,NULL);
    pthread_create(&idw,NULL,write_fun,NULL);

    pthread_join(idr1,NULL);
    pthread_join(idr2,NULL);
    pthread_join(idw,NULL);

    pthread_rwlock_destroy(&rwlock);


    return 0;
}

线程安全

1.strtok函数不能在多线程中使用,因为它非线程安全函数,只能在单线程使用
2.线程安全:多线程程序无论调度顺序如何,都能得到正确的一致的效果
3.线程安全函数:
strtok() 非线程安全函数
strtok_r 线程安全函数
4.解决线程安全问题方法:同步,使用线程安全的函数
5.非线程安全函数由什么原因导致?
在函数内部使用全局变量或静态变量

多线程+fork()

多线程中执行fork(),会将整个进程进行复制,但是在子进程中只启用一条执行路径(fork所在的那条执行路径)

1.多线程 + fork() 不加锁

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
//多线程+fork  不加锁
void* fun(void* arg)
{
    for(int i=0;i<5;i++)
    {
        printf("fun run pid=%d\n",getpid());
        sleep(1);
    }
}

int main()
{
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);

    fork();
    for(int i=0;i<5;i++)
    {
        printf("main run pid=%d\n",getpid());
        sleep(1);
    }
    pthread_join(id,NULL);

    return 0;

}

2.多线程 + fork() 加锁

子进程复制了父进程的mutex,包含其状态,(加锁或未加锁)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/wait.h>

//多线程+fork()+锁
//fork()会复制锁,也就是进程和子进程各用一把锁,另外还会复制锁的状态

pthread_mutex_t mutex;
void* fun(void* arg)
{
    pthread_mutex_lock(&mutex);
    printf("fun lock\n");
    sleep(5);
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");

}

int main()
{
    //初始化互斥锁
    pthread_mutex_init(&mutex,NULL);
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);
    sleep(1);

    pid_t pid = fork();
    if(pid == -1)
    {
        exit(0);
    }
    if(pid == 0)
    {
        printf("child will lock\n");
        pthread_mutex_lock(&mutex);
        printf("child lock\n");
        pthread_mutex_unlock(&mutex);
        printf("child unlock\n");
        exit(0);
    }
    wait(NULL);

}

会阻塞,因为fork时将已经加锁的锁复制了,所以在子进程中加锁失败,就会阻塞到pthread_mutex_lock(&mutex)

解决方案
pthread_atfork的使用说明:
pthread_atfork(void (*prepare)(void),void (*parent)(void), void(*child)(void))
prepare在父进程fork创建子进程之前调用,这里可以获取父进程定义的所有锁;
child fork返回之前在子进程环境中调用,在这里unlock prepare获得的锁;
parent fork创建了子进程以后,但在fork返回之前在父进程的进程环境中调用的,在这里对prepare获得的锁进行解锁;
1、使用场景
当父进程有多线程时,子进程继承父进程所有的互斥量、读写锁和条件变量的状态,子进程只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁(任一线程),子进程同样占有这些锁。如果父进程马上调用exec,老的地址空间被丢弃,所以锁的状态无关紧要。否则,则要清除锁的状态。

原文链接:https://blog.csdn.net/codinghonor/article/details/43737869


//多线程+fork()+锁
//fork()会复制锁,也就是进程和子进程各用一把锁,另外还会复制锁的状态

pthread_mutex_t mutex;

void after_fork()
{
    pthread_mutex_unlock(&mutex);
}
void before_fork()
{
    pthread_mutex_lock(&mutex);
}
void* fun(void* arg)
{
    pthread_mutex_lock(&mutex);
    printf("fun lock\n");
    sleep(5);
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");

}

int main()
{
    //初始化互斥锁
    pthread_mutex_init(&mutex,NULL);

    pthread_atfork(before_fork,after_fork,after_fork);

    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);
    sleep(1);

    pid_t pid = fork();
    if(pid == -1)
    {
        exit(0);
    }
    if(pid == 0)
    {
        printf("child will lock\n");
        pthread_mutex_lock(&mutex);
        printf("child lock\n");
        pthread_mutex_unlock(&mutex);
        printf("child unlock\n");
        exit(0);
    }
    wait(NULL);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值