TCP/IP网络编程——理解线程(临界区、同步)

每个进程的内存空间都有保存全局变量的“数据区”,动态分配内存空间的堆,函数运行时使用的栈。

进程的切换需要上下文切换,开销巨大。

线程共享数据区和堆,只需分离栈区域,上下文切换时开销较小。


下面是多线程求1-10的和的程序:

thread.c

//多进程模型缺点:创建进程过程会带来一定开销(频繁的上下文切换);数据交换需要IPC技术
//上下文切换:如果运行进程A后要切换到运行进程B,就应将A的相关信息移出内存,并读入B的相关信息
//多线程模型优点:上下文切换不需要切换数据区和堆,可以利用数据区和堆交换数据
#include <stdio.h>
#include <pthread.h>

void * thread_summation(void *arg);
int sum=0;

int main(void)
{
  pthread_t id_t1, id_t2;	//线程ID
  int range1[]={1, 5};
  int range2[]={6, 10};
  
  //创建线程
  pthread_create(&id_t1, NULL, thread_summation, (void*)range1);
  pthread_create(&id_t2, NULL, thread_summation, (void*)range2);
  //等待线程终止
  pthread_join(id_t1, NULL);
  pthread_join(id_t2, NULL);
  
  printf("result: %d \n", sum);
  return 0;
}

void* thread_summation(void* arg)
{
  int start=((int *)arg)[0];
  int end=((int *)arg)[1];
  
  while(start<=end)
  {
    sum+=start;	//此处存在临界区相关问题
    start++;
  }
  return NULL;
}

程序执行流程图如下图所示:


但是上述程序存在一个问题:

“2个线程会同时访问全局变量sum”

如下图所示,线程1首先读取该变量num的值并将其传给CPU,获得加1后的结果是100,最后再把结构写回变量num,这样num中就保存100。


但是线程2有可能在线程1还未写入num时拿到num(此时num为99),这样一来最终num的结果就不是101,而是100了(线程12都将100写入num)


操作num的这部分代码就叫临界区,临界区:函数内同时运行多个线程引起问题的多条语句构成的代码块。

解决上述问题的方法就是——线程同步

它解决了两个问题:1.同时访问同一内存空间发生的情况 2.需要指定访问同一内存空间的线程执行顺序的情况

针对问题1——互斥量

互斥量相当于对临界区加了一把锁,阻止多个线程同时访问。

mutex.c

//临界区:函数内同时运行多个线程时引起问题的多条语句构成的代码块
//多个线程同时访问临界区会出问题
//解决方法——线程同步:
//1.同时访问同一内存空间时发生的问题——互斥量
//2.需要指定访问同一内存空间的线程执行顺序的情况——信号量
//互斥量好比一个洗手间,同一时刻只允许一个人使用,有加锁和解锁的过程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <boost/graph/graph_concepts.hpp>

#define NUM_THREAD 100

void * thread_inc(void *arg);
void * thread_des(void *arg);

long long num=0;
pthread_mutex_t mutex;	//创建互斥量

int main(void)
{
  pthread_t thread_id[NUM_THREAD];
  
  pthread_mutex_init(&mutex, NULL);	//初始化
  int i;
  for(i=0;i<NUM_THREAD;i++)
  {
    if(i%2)
      pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
    else
      pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
  }
  
  for(i=0;i<NUM_THREAD;i++)
  {
    pthread_join(thread_id[i], NULL);
  }
  
  printf("reault: %lld \n", num);	//最终会输出0
  pthread_mutex_destroy(&mutex);
  return 0;
}

void* thread_inc(void* arg)
{
  int i;
  pthread_mutex_lock(&mutex);	//加锁
  //临界区开始
  for(i=0;i<500000;i++)
    num+=1;
  //临界区结束
  pthread_mutex_unlock(&mutex);	//解锁
  return NULL;
}

void* thread_des(void* arg)
{
  int i;
  pthread_mutex_lock(&mutex);	//加锁
  for(i=0;i<500000;i++)
    num-=1;
  pthread_mutex_unlock(&mutex);
  return NULL;
}

针对问题2——信号量

控制访问顺序的同步,参考操作系统中的“生产者——消费者”问题。

semaphore.c

//信号量:控制线程访问顺序的同步
//PV操作:信号量在调用sem_post时增1,调用sem_wait时减1
//在信号量为0时调用sem_wait时线程将进入阻塞状态
//生产者与消费者模型
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void * read(void *arg);
void * accu(void *arg);
//定义信号量
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(void)
{
  pthread_t id_t1, id_t2;
  sem_init(&sem_one, 0, 0);	//初始化sem_one为0
  sem_init(&sem_two, 0, 1);	//初始化为1
  
  pthread_create(&id_t1, NULL, read, NULL);
  pthread_create(&id_t2, NULL, accu, NULL);
  
  pthread_join(id_t1, NULL);
  pthread_join(id_t2, NULL);
  
  sem_destroy(&sem_one);
  sem_destroy(&sem_two);
  return 0;
}

void* read(void* arg)
{
  int i;
  for(i=0;i<5;i++)
  {
    fputs("Input number: ", stdout);
    sem_wait(&sem_two);	//占用盘子sem_two=0
    //临界区
    scanf("%d", &num);
    sem_post(&sem_one);	//在盘子上放苹果sem_one=1
  }
}

void* accu(void* arg)
{
  int sum=0, i;
  for(i=0;i<5;i++)
  {
    sem_wait(&sem_one);	//吃掉苹果sem_one=0
    sum+=num;
    sem_post(&sem_two);	//放出盘子sem_two=1
  }
  printf("result: %d \n", sum);
  return NULL;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值