多线程及其同步问题

本文介绍了线程的基本概念,包括线程的定义、引入原因和所占用的资源。详细阐述了用户级线程和内核级线程的特点、优缺点。此外,讨论了线程同步的重要性,列举了如信号量等同步方法,并提到了pthread库在多线程编程中的应用。
摘要由CSDN通过智能技术生成

线程的定义

线程,有时被称为 轻量进程(Lightweight Process,LWP), 是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

为什么要引入线程

在多道程序环境下,程序的执行属于并发执行,此时它们将失去其封闭性。并具有间断性及不可再现性的特征。这就决定了通常的程序是不能并发执行的而程序的顺序执行使系统资源利用率低,为此引入线程。

线程资源

线程本身并不拥有系统资源,仅有一些能保证独立运行 的资源,这块资源的各个线程私有的。

  • 线程各自独立拥有的部分:

  • 线程ID

  • 一组寄存器

  • errno(错误编码)

  • 信号屏蔽字(一个进程中pending(未决)信号只有一个,但是任意一个线程都可以处理这个信号)

  • 调度优先级

  • 共享区域

  • 同一个地址空间(代码、全局数据、堆都是共享的。如果定义一个函数,在每个线程中都可以共享到,如果定义一个全局变量,在任何一个线程中都可以访问到

  • 文件描述符表

  • 每种信号的处理方式

  • 当前工作目录

  • 用户ID和组ID

线程的三种实现方式

用户级线程

特点

  • 用户线程是完全建立在用户空间的线程库,用户线程的创建、调度、同步和销毁无需利用系统调用来实现,而用线程库在用户空间完成,不需要内核的帮助,内核意识不到线程的存在。因此这种线程是极其低消耗和高效的。

线程库

用于用户级线程管理的一个例程包,它包含用于创建和销毁线程的代码、在线程之间传递消息和数据的代码、调度线程执行的代码以及保存和恢复线程上下文的代码。

用户级线程的优点

  • 处理机的竞争: 单纯的用户线程是建立在用户空间,所属进程单独参与处理器的竞争,进程的所有线程竞争该进程的资源

  • 使用资源: 与所属进程共享进程地址空间和系统资源

  • 调度: 有在用户空间的线程库,在所属的进程内进行调度

  • 具有较高的灵活性: 线程的一切(包括调度、创建)都可以完全由用户自己决定

  • 效率高: 由于是在用户态上进行管理,所以就省去了内核管理的开销

  • 线程能够利用的表空间和堆栈空间比内核级线程多

  • 用户级线程包可以在不支持线程的操作系统上实现。

  • 不需要陷阱,不需要上下文切换,也不需要对内存高速缓存进行刷新,使得线程调用非常快捷。

用户级线程的缺点

  • 线程发生I/O或页面故障或页面失效引起的阻塞时,如果调用阻塞系统调用则内核由于不知道有多线程的存在,而会阻塞整个进程从而阻塞所有线程, 因此 同一进程中只能同时有一个线程在运行
  • 由于用户级线程 没有时间片概念, 所以每个线程必须运行一段时间后将CPU让个其他的线程使用,否则,该线程将独占CPU

内核级线程

内核级线程,是指由内核管理的线程。用户应用程序通过API和系统调用(system call)来访问线程工具。

特点

  • 由操作系统管理,所以操作系统是知道线程的存在,并为其安排时间片,管理与其有关的内核对象
  • 这些线程 可以在全系统内进行资源的竞争
  • 线程的创建、撤销和切换等,都需要内核直接实现,即 内核了解每一个作为可调度实体的线程
  • 内核空间内为每一个内核支持线程设置了一个线程控制块(TCB),内核根据该控制块,感知线程的存在,并进行控制

内核级线程的优点

  • 处理机的竞争: 可以在全系统范围内竞争处理器的资源
  • 使用资源: 唯一使用的资源是内核栈和上下文切换时保存寄存器的空间
  • 调度: 调度的开销可能和进程自身差不多昂贵
  • 同步效率: 资源的同步和数据共享币比整个进程的数据同步和共享要低一些

内核级线程的缺点

  • 用户级线程的线程切换需要少量的机器指令,而内核级线程需要完整的上下文切换,修改内存映像。

  • 使高速缓存失效,这导致了若干数量级的延迟。

线程同步问题

一般情况下我们需要创建多个线程以提高效率,但是在多个线程同时运行的过程中有可能对同一个地址进行写入,由于处理器调度的问题,可能会导致写入的数据被多次覆盖,这样程序运行的结果可能就不是我们预期的那样。因此,我们必须实现线程同步。
所谓 线程同步就是: 当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。

线程同步的方法

  • 信号量

  • 互斥锁

  • 条件变量

  • 读写锁

操作函数

sem_init():初始化信号量
sem_wait():以原子操作对信号量的值减1
sem_destroy():删除信号量,并清理该信号量所拥有的所有资源,如果企图清理的信号量正在被一些线程等待,就会收到一个错误

pthread_mutex_init():初始化互斥锁
pthread_mutex_destroy():删除互斥锁
pthread_mutex_unlock():释放互斥锁


pthread_cond_mutex():初始化条件变量
pthread_cond_destroy():销毁条件变量

信号量举例

在这里插入图片描述

pthread库

操纵函数

pthread_create():创建一个线程
pthread_cancle():中断指定线程的运行
pthread_exit():结束当前线程的运行
pthread_join():让该进程等待另一个进程的结束,获取信息
pthread_attr_init():初始化线程的属性
pthread_attr_destroy():删除线程的属性

程序举例

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


void *thread_fun(void *arg)
{
	int i=0;
	for(;i<7;i++)
	{
		printf("fun run\n");
		sleep(1);
	}
    pthread_exit("fun over");
}



int main()
{
	pthread_t id;

	pthread_create(&id,NULL,thread_fun,NULL);//创建线程
    
	int i=0;
	for(;i<3;i++)
	{
		printf("main run\n");
		sleep(1);
	}
	char *s=NULL;
	pthread_join(id,(void **)&s);//主线程等待创建的线程的结束,并获取该线程的退出信息
	printf("s=%s\n",s);
	exit(0);
}

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

#define MAXID  5

void *thread_fun(void *arg)
{
  int index=(int)arg;
  int i=0;
  for(;i<4;i++)
  {
	printf("index=%d\n",index,i);
		sleep(2);
  }
}
int main()
{
	pthread_t id[MAXID];
	int i=0;
	for(;i<MAXID;i++)
	{
		pthread_create(&id[i],NULL,thread_fun,(void *)i);

	}
	for(i=0;i<MAXID;i++)
	{
		pthread_join(id[i],NULL);
	}

   exit(0);

}

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
#include<pthread.h>
#include<semaphore.h>


sem_t sem;
#define MAXID  5
int  g=1;
void *thread_fun(void *arg)
{
  int index=(int)arg;
  int i=0;
  for(;i<1000;i++)
  {
	printf("g=%d\n",g++);
  }
}
int main()
{
	pthread_t id[MAXID];
	int i=0;
	for(;i<MAXID;i++)
	{
		pthread_create(&id[i],NULL,thread_fun,NULL);
	}
	for(i=0;i<MAXID;i++)
	{
		pthread_join(id[i],NULL);
	}
   exit(0);
}

在这里插入图片描述
在这里插入图片描述
加了信号量的程序

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
#include<pthread.h>
#include<semaphore.h>


sem_t sem;
#define MAXID  5
int  g=1;
void *thread_fun(void *arg)
{
  int index=(int)arg;
  int i=0;
  for(;i<1000;i++)
  {
   sem_wait(&sem);
	printf("g=%d\n",g++);
	sem_post(&sem);
  }
}
int main()
{
	pthread_t id[MAXID];
sem_init(&sem,0,1);
	int i=0;
	for(;i<MAXID;i++)
	{
		pthread_create(&id[i],NULL,thread_fun,NULL);
	}
	for(i=0;i<MAXID;i++)
	{
		pthread_join(id[i],NULL);
	}
  sem_destroy(&sem);
   exit(0);
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值