操作系统_并发_2.1_线程创建细解

本文详细解析了线程创建的精髓,包括`pthread_create()`函数的使用、线程参数传递、线程完成与join、不推荐的返回指针实践,以及过程调用方法的对比。重点在于正确处理线程生命周期和数据交互。
摘要由CSDN通过智能技术生成

1、线程创建函数

编写多线程程序的第一步就是创建新线程,因此必须存在某种线程创建接口。

下面是phread.h库文件中声明的线程创建函数pthread_create()

int	pthread_create(	pthread_t *th, 
					const pthread_attr_t *attr, 
					void *(* func)(void *), 
					void *arg);
  • 第一个参数th
    th是指向pthread_t 结构类型的指针,我们利用这个结构与该线程交互,因此需要将它传入pthread_create(),以便于将它初始化

  • 第二个参数attr
    attr参数用于指定该线程可能具有的任何属性。一些例子包括设置栈大小,或关于该线程调度优先级的信息。
    一个属性通过单独调用pthread_attr_init()来初始化。

  • 第三个函数指针参数void *(* func)(void *)
    第三个参数的作用是,这个线程应该在哪个函数中运行,这个指针主要包含以下内容:一个函数名称,它被传入一个类型为 void*的参数,并且它返回一个void*类型的值。
    如果这个函数需要一个整数参数,则声明为整型
    int (*start_routine)(void *)

  • 第四个参数arg
    arg是要传递给线程开始执行的函数的参数。

2、创建线程

创建线程示例

我们创建单个线程,并通过myarg_t结构传递一些参数。对于返回值,使用myret_t类型。

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

typedef struct myarg_t{
	int a;
	int b;
}myarg_t;

typedef struct myret_t{
	int x;
	int y;
}myret_t;

void *mythread(void *arg){
	myarg_t *m = (myarg_t *) arg;
    printf("%d %d\n",m->a,m->b);
    myret_t *r = malloc(sizeof(myret_t));
    r->x = 1;
    r->y = 2;
    return(void *) r;
}

int main(int argc,char *argv[]){
    int rc;
    pthread_t p;
    myret_t *m;
    myarg_t args;
    args.a = 10;
    args.b = 20;
    pthread_create(&p,NULL,mythread,&args);
    printf("return %d %d\n",m->x,m->y);
    return 0;
}

程序运行结果
在这里插入图片描述
编译运行后,带有return的输出函数并没有输出数据,Why?

两种可能,一种是线程未执行完毕,另一种是,线程的值没有返回,我们具体来看一下。

3、线程完成

在上面,我们展示了如何创建一个线程,但是,如果想要等待线程完成,需要做一些特别的事情来等待完成,这里我们通过调用函数pthread_join函数来实现。

当线程执行完毕后,线程已经在pthread_join()函数内等待了,然后会返回,我们可以访问线程返回的值,也就是在myret_t中的内容。

int	pthread_join(pthread_t t, void **res);

该函数有两个参数
第一个pthread_t类型的参数是用于指定要等待的线程,这个变量是由线程创建函数初始化的。
第二个参数是一个指针,指向你希望得到的返回值。如果不需要参数,创建线程时传入NULL就可以了

加入等待线程完成后的代码

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

typedef struct myarg_t{
	int a;
	int b;
}myarg_t;

typedef struct myret_t{
	int x;
	int y;
}myret_t;

void *mythread(void *arg){
	myarg_t *m = (myarg_t *) arg;
    printf("%d %d\n",m->a,m->b);
    myret_t *r = malloc(sizeof(myret_t));
    r->x = 1;
    r->y = 2;
    return(void *) r;
}

int main(int argc,char *argv[]){
    int rc;
    pthread_t p;
    myret_t *m;
    myarg_t args;
    args.a = 10;
    args.b = 20;
    pthread_create(&p,NULL,mythread,&args);
    pthread_join(p,(void **) &m);
    printf("return %d %d\n",m->x,m->y);
    return 0;
}

线程运行结果如下
在这里插入图片描述

从上面的图可以看出,得到了线程的返回值。

不过需要注意的是,必须非常小心如何从线程返回值,特别是,永远不要返回一个指针,并让它指向线程调用栈上分配的东西。我们来试一下,看会发生什么糟糕的现象。

4、线程返回一个指针

void *mythread(void *arg){
	myarg_t *m = (myarg_t *) arg;
    printf("%d %d\n",m->a,m->b);
    myret_t r;
    r.x = 1;
    r.y = 2;
    return (void *) &r;
}

执行结果
在这里插入图片描述
我们将变量r分配在mythread的栈上,但是当它返回时,该值会自动释放。

5、奇怪的方式

对于我们上面的示例,使用pthread_create()创建线程,然后立刻调用pthread_join(),这是创建线程的一种非常奇怪的方式。
事实上,有一个被称为过程调用的方法,可以更简单的完成这个任务。

需要注意到的一点是,并非所有的多线程代码使用join函数。
对于web服务器可能会创建大量工作线程,然后使用主线程接受请求,并将其无限期地传递给工作线程,因此这样的长期程序可能不需要join

但,当创建线程来并行执行特定任务的并行程序,很可能会使用join来确保在退出或进入下一阶段计算之前完成所有这些工作。

Windows2000环境下,创建一个控制台进程,此进程包含n个线程。用这n个线程来表示n个者或写者。每个线程按相应测试数据文件(后面有介绍)的要求进行写操作。用信号量机制分别实现者优先和写者优先的者-写者问题。 者-写者问题的写操作限制(包括者优先和写者优先): 1)写-写互斥,即不能有两个写者同时进行写操作。 2)-写互斥,即不能同时有一个线程,而另一个线程在写。, 3)-允许,即可以有一个或多个者在者优先的附加限制:如果一个者申请进行操作时已有另一个者正在进行操作,则该者可直接开始操作。 写者优先的附加限制:如果一个者申请进行操作时已有另一写者在等待访问共享资源,则该者必须等到没有写者处于等待状态后才能开始操作。 运行结果显示要求:要求在每个线程创建、发出写操作申请、开始写操作和结束写操作时分别显示一行提示信息,以确定所有处理都遵守相应的写操作限制。 2测试数据文件格式 测试数据文件包括n行测试数据,分别描述创建的n个线程者还是写者,以及写操作的开始时间和持续时间。每行测试数据包括四个字段,各个字段间用空格分隔。第一字段为一个正整数,表示线程序号。第二字段表示相应线程角色,R表示者,w表示写者。第三字段为一个正数,表示写操作的开始时间:线程创建后,延迟相应时间(单位为秒)后发出对共享资源的写申请。第四字段为一个正数,表示写操作的持续时间。当线程写申请成功后,开始对共 享资源的写操作,该操作持续相应时间后结束,并释放共享资源。 下面是一个测试数据文件的例子: 2 W 4 5 3 R 5 2 4 R 6 5 5 W 5.1 3
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值