• 线程间通信 - 多线程编程(一)


    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

    Linux系统中的线程间通信方式主要以下几种:
    锁机制:包括互斥锁、条件变量、读写锁、自旋锁
            互斥锁提供了以排他方式防止数据结构被并发修改的方法。互斥锁确保同一时间只能有一个线程访问共享资源。当锁被占用时试图对其加锁的线程都进入阻塞状态(释放CPU资源使其由运行状态进入等待状态)。当锁释放时哪个等待线程能获得该锁取决于内核的调度。

            读写锁允许多个线程同时读共享数据,而对写操作是互斥的。当以写模式加锁而处于写状态时任何试图加锁的线程(不论是读或写)都阻塞,当以读状态模式加锁而处于读状态时“读”线程不阻塞,“写”线程阻塞。读模式共享,写模式互斥。

            自旋锁上锁受阻时线程不阻塞而是在循环中轮询查看能否获得该锁,没有线程的切换因而没有切换开销,不过对CPU的霸占会导致CPU资源的浪费。 所以自旋锁适用于并行结构(多个处理器)或者适用于锁被持有时间短而不希望在线程切换产生开销的情况。

            条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。这个主要是使用锁的机制实现,所以划到锁这个里面了。

    信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
    信号机制(Signal):类似进程间的信号处理

    2. 具体实现

    2.1 信号量机制

    1. 简介
    1.1信号量简介
    Linux下主要分为两种信号量,system-v和posix信号量,posix信号量又分为无名信号量和有名信号量,这里我们只分享无名信号量这里我们主要研究posix信号量。信号量是同步的一种方式,常常用于对共享资源的访问,举一个常见的例子,假如一个停车场有100个停车位,我们将车停在这个停车场需要看一下这个停车场还有多少车位可以停,此时空位就是一个信号量,每空一个车位,信号量+1,每次停一辆车时信号量-1.

    1.2 信号量特点
    (1)信号量会使等待资源线程进入休眠状态,所以适合于那些占用资源比较久的场合,比较车倒车状态的切换。
    (2)在驱动中,信号量不可以用于中断中,原因是信号量会引起休眠状态,中断不能休眠。
    (3)若共享资源的持有时间比较短,则不适合使用信号量,频繁的休眠,切换线程会导致开销远远大于信号量所带来的那点优势。

    2.相关操作函数API
    2.1 初始化

    #include
    int sem_init(sem_t *sem, int pshared,unsigned int value);
    1
    2
    sem :信号量变量;
    pshared:是否在多个进程中传递信号量,若不是则写为0;
    value:该参数指定信号量的初始值。如果想要在两个进程之间使用信号量,需要确保sem参数指向两个进程之间共享的内存范围。

    2.2 销毁

    #include
    int sem_destroy(sem_t *sem);
    1
    2
    2.3 P操作

    #include
    int sem_trywait(sem_t *sem);
    int sem_wait(sem_t *sem);
    returns 0 on success; 
    on error,-1 is returned, 
    1
    2
    3
    4
    5
    如果信号量的值大于0,则此时会将信号量的值减1,并且立刻返回,若当前信号量的值为0,那么sem_wait会导致线程阻塞,直到信号量的值大于0或者被信号中断才返回,而sem_trywait为非阻塞版本,当信号量为0时,该函数回-1并且将errno置为EAGAIN。

    2.4 V操作

    #include
    int sem_post(sem_t *sem);
    returns 0 on success;
    on error,-1 is returned, 
    1
    2
    3
    4
    该函数用于给信号量计数+1,若此时用sem_wait阻塞的线程则被唤醒。

    2.5 取值操作

    #include
    int sem_t getvalue(sem_t sem, int *val);
    returns 0 on success;
    on error,-1 is returned, 
    1
    2
    3
    4
    val返回的值

    3.简单例子
    #include
    #include
    #include
    #include
    #include

    sem_t g_sem;


    void *ReadThreadFun(void *arg)
    {
        while(1)
        {
            printf("read 1 begin\n");
            sem_wait(&g_sem);
            printf("read 1 end\n");
        }
    }

    void *WriteThreadFun(void *arg)
    {
        while(1)
        {
            sleep(1);
            printf("write 1 begin\n");
            sem_post(&g_sem);
            printf("write 1 end\n");
        }
    }

    int main(int argc,char **argv)
    {
        pthread_t pReadID;
        pthread_t pWriteID;
        int iRet;
        
        iRet = sem_init(&g_sem,0,0);
        if(iRet != 0)
        {
            printf("sem_init failed\n");
            return 0;
        }
        
        iRet = pthread_create(&pReadID,NULL,ReadThreadFun,NULL);
        if(iRet !=0 )
        {
            printf("ReadThreadFun create failed\n");
            return -1;
        }
        
        iRet = pthread_create(&pWriteID,NULL,WriteThreadFun,NULL);
        if(iRet !=0 )
        {
            printf("WriteThreadFun create failed\n");
            return -1;
        }
        
        pthread_join(pReadID,NULL);
        pthread_join(pWriteID,NULL);
        
        sem_destroy(&g_sem);
        
        return 0;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    结果:

    read 1 begin
    write 1 begin
    write 1 end
    read 1 end
    read 1 begin
    write 1 begin
    write 1 end
    read 1 end
    ....
    1
    2
    3
    4
    5
    6
    7
    8
    9
    当read线程开始时,此时sem的值为0,阻塞此线程,write线程开始,此时sem的值变为0,解除线程阻塞
    ————————————————
    版权声明:本文为CSDN博主「深海带鲤鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_43824344/article/details/112147273

    2.2

    信号量机制有专门的函数直接告知wait等待的线程,然后等待线程操作数据结束

    在这个例子中,线程C因调用了监控对象的wait()方法而挂起,线程D通过调用监控对象的notify()方法唤醒挂起的线程C。我们还可以看到,两个线程都是在同步块中调用的wait()和notify()方法。如果一个线程在没有获得对象锁的前提下调用了这个对象的wait()或notify()方法,方法调用时将会抛出 IllegalMonitorStateException异常。

    注意,当一个线程调用一个对象的notify()方法,则会唤醒正在等待这个对象所有线程中的一个线程(唤醒的线程是随机的),当线程调用的是对象的notifyAll()方法,则会唤醒所有等待这个对象的线程(唤醒的所有线程中哪一个会执行也是不确定的)。

    这里还有一个问题,既然调用对象wait()方法的线程需要获得这个对象的锁,那么这会不会阻塞其它线程调用这个对象的notify()方法呢?答案是不会阻塞,当一个线程调用监控对象的wait()方法时,它便会释放掉这个监控对象锁,以便让其它线程能够调用这个对象的notify()方法或者wait()方法。

    另外,当一个线程被唤醒时不会立刻退出wait()方法,只有当调用notify()的线程退出它的同步块为止。也就是说,被唤醒的线程只有重新获得监控对象锁时才会退出wait()方法,因为wait()方法在同步块中,它的执行需要再次获得对象锁。所以,当通过notifyAll()方法唤醒被阻塞的线程时,一次只能有一个线程会退出wait()方法,同样是因为每个线程都需要先获得监控对象锁才能执行同步块中的wait()方法退出。


    ————————————————
    版权声明:本文为CSDN博主「假装自己会Python」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/m0_51958727/article/details/120456233

  • 相关阅读:
    JSP pagecontext对象的简介说明
    集合框架----源码解读HashSet篇
    定时脚本自动自动将文件push到git
    【无标题】ssm
    ISAC通信感知一体化学习记录
    ES6 入门教程 5 字符串的新增方法 5.7 实例方法:padStart(),padEnd() ~ 5.11 实例方法:at()
    TensorFlow 张量
    Java模拟抽奖。奖池有以下几个奖项:【2,1888,588,388,2888】打印出抽奖结果,要求随机且不重复。两种方法(代码和优化后的代码)
    通过vcsa修改esxi root密码
    2023年DDoS攻击暴增170%:美国、中国和印度是重灾区
  • 原文地址:https://blog.csdn.net/u012294613/article/details/126320327