b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

Linux多线程编程

电脑杂谈  发布时间:2019-06-18 09:12:37  来源:网络整理

多线程编程_线程编程主进程_四核四线程和四核八线程

前言:有这样一道面试题(来自):

“编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。”

我们就从这样一题出发,认识多线程,了解其同步机制,最后正确解答这一类题目。本文框架如下:

一.进程与线程

进程的定义:进程是为了描述程序在并发执行时对系统资源的共享,所需的一个描述程序执行时动态特征的概念。进程是具有独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配、调度和保护的独立单位。

线程的定义:线程也成为轻量级进程,是进程中的一个运行实体,作为CPU的调度单位。一个进程由多个线程组成,线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

同一进程内的所有线程除了共享全局变量外还共享:

每个线程拥有各自的:

结构上的不同可以让我们更加了解进程和线程的相异之处:

(1)进程是资源分配的基本单位;线程与资源分配无关,它属于某一个进程,并与进程内的其他线程共享进程的资源。

(2)当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

(3)线程只由先关堆栈(系统栈或用户栈)寄存器和线程控制块组成。寄存器用来存储线程内的局部变量,但不能存储其他线程的相关变量。

这里就说说在删除节点时要注意的地方,首先你要找到要删除的那个节点,然后把要删除节点的下一个节点的地址保存好(这里保存到pt),然后释放该节点的内存,让改指针指向null指针(这里因为马上要用到这个指针就没有让它指向null指针),接着让被删除节点的上一个的指针指向上面保存好的节点地址(也就是pt),这样节点被删除了,内存也释放了,链表也连接起来了。系统对内存的访问是直接的,操作系统对内存空间没有保护,多个进程可共享一个运行空间,所以,即使是一个无特权进程调用一个无效指针也会触发一个地址错误,并有可能引起程序崩溃甚至系统崩溃。系统 对内存的访问是直接的,操作系统对内存空间没有保护,多个进程可共享一个运行空间多线程编程,所 以,即使是一个无特权进程调用一个无效指针也会触发一个地址错误,并有可能引起程序崩 溃甚至系统崩溃。

二.多线程的优越性

四核四线程和四核八线程_线程编程主进程_多线程编程

在传统的UNIX模型中,当一个进程需要另一个实体来完成某事,它就fork一个子进程并让子进程去处理。但是fork的调用有如下缺点:

(1)fork的代价是昂贵的。fork要把父进程的内存印象复制到子进程,并在子进程中复制所有描述符等。

当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程id和父进程id,其他的都是一样.就象符进程克隆(clone)自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回值. 当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用.对于父进程fork返回子进程的id,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的. 父进程为什么要创建子进程呢。实际上是在父进程中执行fork()函数时,父进程会复制一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行,从而使两个进程分别获得所属fork()函数的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0。返回两次分别是在父进程和子进程中各返回一个值,在子进程中返回为0,在父进程中返回进程id,一般为正整数即非零。

针对这两点,多线程技术相应而生,它具有如下优越性:

(1)它是一种非常"节俭"的多任务操作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

支持多线程的程序(进程)可以取得真正的并行(parallelism),且由于共享进程的代码和全局数据,故线程间的通信是方便的。而线程的创建就花费少得多,并且同一进程内的线程共享全局存储区,所以通讯方便。同一进程中可以包含多个线程,由于进程中的多个线程可以共享进程中的资源,所以使同一进程中的多个线程之间通信相对比较简单。

三.基本线程函数

1.pthread_create函数,创建线程

int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);

当创建线程成功时多线程编程,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为eagain和einval.前者表示系统限制创建新的线程,例如线程数目过多了。wait(.(父窗口标题,控件文本,父窗口类名,控件类名,控件id)=等待指定窗口创建,所有参数可选\n成功返回窗口句柄,控件句柄,线程id,进程id,超时返回空值。这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是作用域或指向作用域的指针,作用域控制线程是否在进程内或在系统级上竞争资源,可能的值。

const pthread_attr_t *attr:每个线程有多个属性,包括优先级、初始栈大小、是否是一个守护线程等等。

void *(*func)(void *):线程启动函数,线程从调用这个函数开始,或显示结束(调用pthread_exit()),或隐式结束(让该函数返回)。

void *arg:线程执行func函数的传递参数。

2.pthread_join函数,等待一个线程终止

四核四线程和四核八线程_多线程编程_线程编程主进程

int pthread_join(pthread_t *tid, void **status);

void **status:二级指针,如果status指针非空,那么所等待线程的返回值将存放在status指向的位置。

3.pthread_self函数,返回线程ID

int pthread_self(void);

跟进程比较,相当于getpid。

4.pthread_detach函数,线程分离

int pthread_detach(pthread_t tid);  

线程或者是可汇合的(joinable),或者是脱离的(detach)。当可汇合的线程终止时,线程ID和退出状态将保留,知道另外一个线程调用pthread_join。脱离的线程终止时,释放所有的资源,因此我们不能等待它终止。若要一个线程知道另一个线程的终止时间,我们就要保留第二个线程的可汇合性。

5.pthread_exit函数,线程终止

int pthread_exit(void **status);  

若线程未脱离,那么它的线程ID和退出状态将保留到另外一个线程调用pthread_join为止。

四.多线程的同步

有了上面的基本函数还不足以完成本题的要求,为什么呢?因为题目要求按照ABCABC...的方式打印,而3个线程却在抢占资源,所以无法控制排列顺序。这时就需要用到多线程编程中的同步技术。

对于多线程编程来说,同步就是同一时间只允许一个线程访问资源,而其他线程不能访问。多线程有3种同步方式:

线程编程主进程_多线程编程_四核四线程和四核八线程

1.互斥锁

互斥锁是最基本的同步方式,它用来保护一个“临界区”,保证任何时刻只由一个线程在执行其中的代码。这个“临界区”通常是线程的共享数据。

下面三个函数给一个互斥锁上锁和解锁:

int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_trylock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);

假设线程2要给已经被线程1锁住的互斥锁(mutex)上锁(即执行pthread_mutex_lock(mutex)),那么它将一直阻塞直到到线程1解锁为止(即释放mutex)。

创建一个数组,动态指定数组的大小(在程序运行过长中,可以随意的开辟指定大小的内存,以供使用,相当于java中的集合)*静态内存分配,分配内存大小的是固定,问题:1.很容易超出栈内存的最大值 2.为了防止内存不够用会开辟更多的内存,容易浪费内存动态内存分配,在程序运行过程中,动态指定需要使用的内存大小,手动释放,释放之后这些内存还可以被重新使用(活水)函数:calloc() 分配内存空间并初始化calloc() 函数用来动态地分配内存空间并初始化为 0,其原型为:。互斥锁管理:1.互斥锁属性控制(范围、属性、协议、优先级、强健)2.互斥锁基本操作(锁定、解锁、非阻塞锁定)使用宏初始化静态分配的互斥锁:不需要调用pthread_mutex_initpthread_mutex_t lock = pthread_mutex_initializer。对于一个已上锁的互斥锁,若调用pthread_mutex_lock()函数再次加锁,将使调用线程阻塞,直到互斥锁被解锁.调用成功发那会0,失败返回-1.参数mutex是pthread_mutex_t数据类型的指针。

2.条件变量

互斥锁用于上锁,而条件变量则用于等待,通常它都会跟互斥锁一起使用。

int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);

通常pthread_cond_signal只唤醒等待在相应条件变量上的一个线程,若有多个线程需要被唤醒呢,这就要使用下面的函数了:

int pthread_cond_broadcast(pthread_cond_t *cptr);

3.读写锁

互斥锁将试图进入连你姐去的其他简称阻塞住,而读写锁是将读和写作了区分,读写锁的分配规则如下:

(1)只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读;

四核四线程和四核八线程_线程编程主进程_多线程编程

(2)仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写。

int pthread_rwlock_rdlock(pthread_relock_t *rwptr);
int pthread_rwlock_wrlock(pthread_relock_t *rwptr);
int pthread_rwlock_unlock(pthread_relock_t *rwptr);

五.题目代码

分析此题:

1.主线程main创建3个线程tid0,tid1,tid2;

2.设一个全局变量num,互斥锁mutex保护此临界区保证每次只有一个线程访问num;

3.若抢占到资源的线程tid并不是我们需要的,那么让它阻塞;

4.若抢占到资源的线程tid正好是我们需要的,那么就打印相应字母;

5.解锁,唤醒其他两个等待线程;

6.main函数等待3个线程打印结束才结束。

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<unistd.h>
#include<pthread.h>
int num=0;
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void *func(void *);
int main()
{
	pthread_t tid[3];
	int ret=0,i;
	for(i=0;i<3;i++)
		if((ret=pthread_create(&tid[i],NULL,func,(void*)i))!=0)
			printf("create thread_%c error\n",i+'A');
   	for(i=0;i<3;i++)
		pthread_join(tid[i],NULL);
	printf("\n");
	return 0;
}
void *func(void *argc)
{
	int i;
	for(i=0;i<10;i++)
	{
		pthread_mutex_lock(&mutex);
	    while(num!=(int)argc)
			pthread_cond_wait(&cond,&mutex);
		printf("%c",num+'A');
		num=(num+1)%3;
		pthread_mutex_unlock(&mutex);
		pthread_cond_broadcast(&cond);
	}
	pthread_exit(0);
}

参考资料:

1. 《UNIX网络编程卷2》

2.

3.


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-106821-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...