多线程与信号量简介

信号量与 PV 操作

计算机中信号量的本质是整数,数值表示可用的资源数量

P 操作 (Passeren => 通过, 原子操作)

  • 若信号量 == 0,当前任务阻塞 (进入信号量等待队列)
  • 若信号量 > 0,则:将信号量数值减一,当前任务继续执行

V 操作 (Vrijgeven => 释放, 原子操作)

  • 将信号量数值加一
  • 若 信号量 > 0,则:唤醒阻塞的其它任务,当前任务继续执行

信号量与 PV 操作注意事项

程序中的 PV 操作必须成对出现 (P 操作 => 临界区 => V 操作)

信号量初始值一般为 1 (初始值与相应资源数量相关)

信号量也看做特殊的互斥量 (信号量初始值为 1 时,退化为互斥量)

若 信号量 == S && S > 0,则:可进行 P 操作并且不阻塞的次数为 S

信号量模拟用法示例

Linux 中的信号量

下面的程序输出什么?为什么?

test1.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

void* customer_thread(void* arg)
{   
    printf("thread begin %ld\n", pthread_self());
    
    sem_wait(arg);
    
    printf("thread end %ld\n", pthread_self());
        
    return NULL;
}

int main()
{
    pthread_t t = {0};
    sem_t sem = {0};
    int i = 0;
    int v = 0;

    sem_init (&sem, PTHREAD_PROCESS_PRIVATE, 1);
    
    sem_getvalue(&sem, &v);
    
    printf("sem = %d\n", v);

    for(i=0; i<5; i++)
    {
        pthread_create(&t, NULL, customer_thread, &sem);
    }
    
    sleep(5);
    
    sem_getvalue(&sem, &v);
    
    printf("sem = %d\n", v);

    printf("End!\n");
  
    return 0;
}

第 28 行,初始化信号量,将信号量的初始值设置为 1,那么这里的信号量等同与互斥锁

第 36 行,主线程创建了 5 个子线程,子线程通过 sem_wait() 去获取信号量

第 41 行,主线程通过 sem_getvalue() 来获取当前信号量的值

由于信号量的初始值为 1,所以只能有一个子线程获取到信号量,其它的线程来获取信号量时,发现信号量的值为 0,就会阻塞等待信号量被释放

程序运行结果如下图所示:

只有一个子线程获取到了信号量,最后信号量的值为 0

生产消费者问题示例

信号量初体验

我们使用信号量来解决生产者消费者问题

test3.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <semaphore.h>

typedef struct
{
    sem_t r;
    sem_t w;
} Sem;

void* customer_thread(void* arg)
{   
    Sem* s = arg;  
    
    sleep(1);
    
    while( 1 )
    {
        sem_wait(&s->r);
        
        printf("%s : get\n", __FUNCTION__);
        
        sem_post(&s->w);
    }
        
    return NULL;
}

int main()
{
    pthread_t t = {0};
    Sem sem = {0};
    
    sem_init (&sem.r, PTHREAD_PROCESS_PRIVATE, 0);
    sem_init (&sem.w, PTHREAD_PROCESS_PRIVATE, 1);

    pthread_create(&t, NULL, customer_thread, &sem);
    
    printf("Hello World!\n");
    
    while( 1 )
    {
        sem_wait(&sem.w);
        
        printf("%s : set\n", __FUNCTION__);
        
        sem_post(&sem.r);
        
        sleep(3);
    }

    printf("End!\n");
  
    return 0;
}

该程序中使用的两个信号量,信号量 w 和信号量 r,信号量 w 的初始值为 1,信号量 r 的初始值为 0

只有主线程写完以后,信号量 r 的值会加一,子线程才能去读;子线程读完以后,信号量 w 的值会加一,主线程才能去写;会一直重复这个流程

程序运行结果如下图所示:

思考

进程之间是否需要进行同步与互斥?

多进程场景

多个进程共享一段内存,即:读写共享内存

此时共享内存的访问就是临界区访问

因此,需要对临界区进行保护 (防止多个进程同时写操作)

问题:是否存在跨进程使用的互斥量?

多进程内存共享

Linux 中的跨进程信号量

多进程内存共享

多进程与信号量

test5.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define SEM_NAME  "delphi_tang"
#define PATH_NAME "/home/book/Documents"
#define PROJ_ID   199

int get_shared_memory(key_t k)
{
    int ret = shmget(k, 0, 0);
    
    if( ret == -1 )
    {
        ret = shmget(k, 
                     128,  
                     IPC_CREAT | IPC_EXCL | S_IRWXU);
    } 
    
    return ret;
}

sem_t* get_sem(int v)
{
    sem_t* ret = sem_open(SEM_NAME, 0);
    
    if( ret == SEM_FAILED )
    {
        ret = sem_open(SEM_NAME, 
                       O_CREAT | O_EXCL, 
                       S_IRWXU, 
                       v);
    }
    
    return ret;
}

int main(int argc, char* argv[])
{
    key_t k = ftok(PATH_NAME, PROJ_ID);  
    char* shmaddr = NULL;
    sem_t* sem = NULL;
    int shmid = get_shared_memory(k);
    
    printf("shmid = %d\n", shmid);
    
    if( shmid == -1 )
    {
        printf("shmget error\n");
        exit(1);
    } 
    
    sem = get_sem(1);
    
    if( sem )
    {
        printf("sem is %p\n", sem);
    }
    
    shmaddr = shmat(shmid, NULL, 0);
    
    while( (argc > 1) && shmaddr )
    {
        static int i = 0;
        
        if( strcmp(argv[1], "write") == 0 )
        {
            sem_wait(sem);
            sprintf(shmaddr, "shared string %d", i++);
            printf("write: %s\n", shmaddr);
            sem_post(sem);
            usleep(1000 * 1000);
        }
        else if( strcmp(argv[1], "read") == 0 )
        {
            sem_wait(sem);
            printf("read: %s\n", shmaddr);
            sem_post(sem);
            usleep(250 * 1000);
        }
        else
        {
            break;
        }
    }

    printf("Press any key to finish process...\n");
    
    system("read -s -n 1");
    
    shmctl(shmid, IPC_RMID, NULL); 
    
    sem_close(sem);
    
    return 0;
}

第 50 行,使用 ftok() 函数用于生成System V IPC(Inter-Process Communication,进程间通信)对象(如信号量、消息队列和共享内存)的键(key)

第 53 行,get_shared_memory() 函数调用了 shmget() 函数,shmget() 是一个Linux系统调用函数,用于创建一个新的共享内存段(segment)或获取一个已存在的共享内存段。这个函数会返回一个整数类型的共享内存标识符(ID),用于在后续的系统调用中引用共享内存段

第 63 行,get_sem() 函数调用了 sem_open() 函数,sem_open() 用于打开或创建一个命名信号量

第 70 行,shmat() 是一个Linux系统调用函数,用于将一个共享内存段附加到当前进程的地址空间。这使得进程可以通过指针访问共享内存段中的数据。shmat() 函数通常在调用 shmget() 函数后使用,以便将获得的共享内存段附加到进程的地址空间

第 72 行 - 95 行,通过信号量来访问多进程中的共享内存

程序运行结果如下图所示:

运行了 2 个进程,一个进程写共享内存,一个进程读取共享内存,信号量用于同步多进程间共享内存的读写

跨进程的信号量是通过文件的方式实现的,创建出的跨进程信号量的文件位置存放在 /dev/shm/目录下面

Linux 信号量的注意事项

信号量之间不能相互初始化,也不能相互赋值 (行为未定义)

跨进程信号量通过文件的方式实现,因此涉及读写权限

sem_close() 仅仅关闭信号量,信号量未删除

sem_unlink() 延迟删除信号量 (/dev/shm/)

  • 即:所有访问信号量的信号量结束后,信号量才被删除
  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值