Bootstrap

线程与进程(5)

目录

信号量(线程的同步 )

    信号量的分类:

    框架:

(1)信号量的定义(semaphore): 

(2)信号量的初始化:

(3)信号量的PV 操作

  (4)信号量的销毁

进程间的通信

通信方式分类:

管道

无名管道(pipe函数):

管道的读写规则:


信号量(线程的同步 )

  • 同步 ===》有 一定先后顺序的 对资源的排他性访问。
  • 互斥 ===》在多线程中对临界资源的排他性访问。

 linux下的线程同步  ===》信号量机制 ===》semaphore.h   posix sem_open();   

    信号量的分类:

  • 信号无名量 ==》线程间通信
  • 有名信号量 ==》进程间通信

    框架:

  1. 信号量的定义     sem_t  sem //造了一类资源 
  2. 信号量的初始化   sem_init 
  3. 信号量的PV操作  (核心) sem_wait()/ sem_post()
  4. 信号量的销毁。   sem_destroy

(1)信号量的定义(semaphore): 

      sem_t  sem;
信号量的类型信号量的变量
sem_t sem_w;
sem_t sem_r;

(2)信号量的初始化:

 int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:将已经定义好的信号量赋值。
参数:sem 要初始化的信号量
           pshared = 0 ;表示线程间使用信号量;pshared!=0 ;表示进程间使用信号量
           value 信号量的初始值,一般无名信号量,都是二值信号量0 /1;0 表示红灯,进程暂停阻塞;
1 表示绿灯,进程可以通过执行
返回值:成功  0; 失败  -1;

sem_init(&sem_w, 0, 1);
sem_init(&sem_r, 0, 0);

(3)信号量的PV 操作

  • P ===》申请资源===》申请一个二值信号量 sem_wait();
  • V ===》释放资源===》释放一个二值信号量 sem_post();

    int sem_wait(sem_t *sem); //P操作 

功能:
          判断当前sem信号量是否有资源可用。
          如果sem有资源(==1),则申请该资源,程序继续运行
          如果sem没有资源(==0),则线程阻塞等待,一旦有资源
          则自动申请资源并继续运行程序。

          注意:sem 申请资源后会自动执行 sem = sem - 1;
参数:sem 要判断的信号量资源
返回值:成功 0 ;失败 -1

 int sem_post(sem_t *sem); //V操作

功能:
          函数可以将指定的sem信号量资源释放
          并默认执行,sem = sem+1;
          线程在该函数上不会阻塞。
参数:sem 要释放资源的信号量
返回值:成功 0
            失败 -1;

  (4)信号量的销毁

int sem_destroy(sem_t *sem);

功能:使用完毕将指定的信号量销毁
参数:sem要销毁的信号量
返回值:成功 0;失败  -1;

练习;用多线程程序设计一个火车票售票系统,

        要求:1. 至少有两个售票窗口,每个售票窗口
                2. 不能重复买票,将100张车票均匀的从两个窗口卖出即可。

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <semaphore.h>

#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int ticket = 100;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#if 0
void *doWin1(void *arg)
{
	int i = 0;
	while (ticket)
	{
		pthread_mutex_lock(&mutex);
		ticket--;
		printf("win1 sell ticket = %d\n",100-ticket);
		pthread_mutex_unlock(&mutex);
	}

	pthread_exit(NULL);
}
#endif 
void *doWin1(void *arg)
{
	int i = 0;
	pthread_mutex_lock(&mutex);
	while (ticket)
	{
		ticket--;
		printf("win1 sell ticket = %d\n",100-ticket);
		pthread_mutex_unlock(&mutex);
	}

	pthread_exit(NULL);
}

void *doWin2(void *arg)
{
	int i = 0;
	pthread_mutex_lock(&mutex);
	while (ticket)
	{
		ticket--;
		printf("win2 sell ticket = %d\n",100-ticket);
		pthread_mutex_unlock(&mutex);
	}

	pthread_exit(NULL);
}


int main(int argc, const char *argv[])
{
	pthread_t tid[2];

	int ret = pthread_create(&tid[0],NULL,doWin1,NULL);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");
	
	ret = pthread_create(&tid[1],NULL,doWin2,NULL);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");


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

进程间的通信

通信方式分类:

(1)不同主机之间(基于内存)

        1)古老的通信方式:管道(无名管道、有名管道);信号;

        2)IPC对象通信(改进):【a:消息队列(用的相对少,这里不讨论)】;【b:共享内存//最高效】;【c:信号量集//信号量】;

(2)不同主机之间(socket 网络部分);

管道

  • 无名管道 ===》pipe ==》只能给有亲缘关系进程通信
  • 有名管道 ===》fifo ===》可以给任意单机进程通信(同一主机内)

特性:

  1. 管道是半双工的工作模式
  2. 所有的管道都是特殊的文件不支持定位操作。lseek->> fd  fseek ->>FILE* 数据流 --- FIFO(first in first out)
  3. 管道是特殊文件,读写使用文件IO。 fgets,fread,fgetc,open,read,write,close;;
无名管道(pipe函数):

特性:【亲缘关系进程使用】;【有固定的读写端】;

流程:

创建并打开管道: pipe函数

int pipe(int pipefd[2]);

功能:创建并打开一个无名管道

参数:

  • pipefd[0] ==>无名管道的固定读端//0 -- 标准输入, 
  • pipefd[1] ==>无名管道的固定写端//1 -- 标准输出 

 返回值:成功 0;失败 -1;

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

int main(int argc, char *argv[])
{
    int fd[2];

    if(pipe(fd) < 0)
    {
        perror("pipe fail");
        return -1;
    }

    printf("fd[0] = %d\n", fd[0]);
    printf("fd[1] = %d\n", fd[1]);

    char buf[] = "hello world";
    write(fd[1], buf, strlen(buf));

    char s[100] = {0};
    read(fd[0], s, sizeof(s));

    printf("s = %s\n", s);
    return 0;
}

注意:无名管道的架设应该在fork之前进行

管道的读写规则:
读端:pipefd[0]写端:pipefd[1]结果
存在存在写管道:管道空:可以写数据
               管道满:会造成-->写阻塞 
不存在存在写管道:系统会给进程发一个信号SIGPIPE(管道破裂)
存在存在读管道:管道空,读不到数据,这时会造成读操作阻塞
存在不存在读管道:如果管道中有数据,则读取这些数据;如果没有数据,读操作不阻塞,立即返回!

        

;