Posix多线程编程(1)—线程基础

一.什么是线程
在一个程序里的多个执行路线就叫做线程。更准确的定义是:线程是一个进程内部的一个控制序列

典型的unix进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程以后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每个线程处理各自独立的任务。


二.线程的优点

1)通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。

2)多个线程可以自动共享相同的存储地址空间和文件描述符。

3)有些问题可以通过将其分解从而改善整个程序的吞吐量。

4 交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中处理用户输入输出的部分与其它部分分开。


三.线程的缺点

线程也有不足之处。编写多线程程序需要更全面更深入的思考。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。调试一个多线程程序也比调试一个单线程程序困难得多。


四.线程的结构

线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽子,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存、栈以及文件描述符。


五.线程标识

就像每个进程有一个进程ID一样,每个线程也有一个线程ID,进程ID在整个系统中是唯一的,但线程不同,线程ID只在它所属的进程环境中有效。线程IDpthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread_t数据类型,所以可以移植的操作系统不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。

函数1

名称:
pthread_equal
功能: 比较两个线程
ID
头文件:
#include <pthread.h>
函数原形:
int pthread_equal(pthread_t tid1,pthread_t tid2);
参数: tid1 进程1id tid2 进程
2id
返回值: 若相等返回非0值,否则返回
0
函数2

名称:
pthread_self
功能: 获取自身线程的
id
头文件:
#include <pthread.h>
函数原形:
pthread_t pthread_self(void);
参数:

返回值: 调用线程的线程
id
六.线程的创建

函数3

名称:
pthread_create
功能: 创建线程

头文件:
#include <pthread.h>
函数原形:
int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);
参数:

返回值: 若成功返回则返回0,否则返回错误编号


pthread_creat成功返回时, tidp指向的内存单元被设置为新创建线程的线程IDattr参数用于定制各种不同的线程属性。可以把它设置为NULL,创建默认的线程属性。

新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。


#include <pthread.h>

#include <stdio.h>
void printids(const char *s)
{
printf(“%s pid:%u  tid:%u /n“, getpid(),pthread_self());
}

void *thr_fn(void *arg)
{
printids (“new thread: “);
}

int main()
{
int err;
pthread_t tid;
err=pthread_create(&tid,NULL,thr_fn,NULL);
if(err=0)
    printf(“can’t create thread:%s/n”,strerror(err));
printids(“main thread: “);
sleep(1);
exit(0);
}
关于进程的编译我们都要加上参数 –lpthread 否则提示找不到函数的错误。

具体编译方法是
cc –lpthread –o gettid gettid.c
运行结果为

main thread: pid 14954 tid 134529024
new thread: pid 14954 tid 134530048

进程的所有信息对该进程的所有线程是共享的----验证

线程的终止
线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。

1)线程只是从启动例程中返回,返回值是线程的退出码。

2)线程可以被同一进程中的其他线程取消。

3)线程调用
pthread_exit.
函数4

名称:
pthread_exit
功能: 终止一个线程

头文件:
#include <pthread.h>
函数原形:
void pthread_exit(void *rval_ptr);
参数:

返回值:

rval_prt
是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以调用pthread_join函数访问到这个指针。

函数5

名称:
pthread_join
功能: 获得线程的终止状态

头文件:
#include <pthread.h>
函数原形:
int pthread_join(pthread_t thread,void **rval_ptr);
参数:

返回值: 若成功返回0,否则返回错误编号。


当一个线程通过调用pthread_exit退出或者简单地从启动历程中返回时,进程中的其他线程可以通过调用pthread_join函数获得进程的退出状态。调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。(直到指定的线程返回)

如果线程只是从它的启动历程返回,rval_ptr将包含返回码。

#include <pthread.h>
#include <string.h>

#include <stdio.h>
void *thr_fn1(void *arg)
{
printf(“thread 1 returning/n”);
return((void *)1);
}
void *thr_fn2(void *arg)
{
printf(“thread 2 exiting/n”);
return((void *)2);
}
int main()
{
pthread_t tid1,tid2;
void *tret;
pthread_create(&tid1,NULL,thr_fn1,NULL);
pthread_create(&tid2,NULL,thr_fn2,NULL);
pthread_join(tid1,&tret); //blocked until tid1 return
printf(“thread 1 exit code %d/n”,(int)tret);
pthread_join(tid2,&tret); //blocked until tid2 return
printf(“thread 2 exit code %d/n”,(int)tret);
exit(0);
}

运行结果是:

thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2


函数6
名称: pthread_detach
功能: 使线程进入分离状态。

头文件:
#include <pthread.h>
函数原形:
int pthread_detach(pthread_t tid);
参数:

返回值: 若成功则返回0,否则返回错误编号。


在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL.pthread_detach调用可以用于使线程进入分离状态。

线程的分离状态决定一个线程以什么样的方式来终止自己,分离状态或者非分离状态。线程的默认属性,一般是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源.程序员应该根据自己的需要,选择适当的分离状态。

函数7
名称:
pthread_cancel
功能: 取消同一进程中的其他线程

头文件:

#include <pthread.h>
函数原形:
int pthread_cancel(pthread_t tid);
参数: tid  线程
id
返回值: 若成功返回0,否则返回错误编号。


在默认的情况下,pthread_cancel函数会使由tid标识的线程的行为表现为如同调用了参数为PTHEAD_CANCELEDpthread_exit函数,但是,线程可以选择忽略取消方式和控制取消方式。pthread_cancel并不等待线程终止,它仅仅提出请求。


函数8
名称:
pthread_cleanup_push/ pthread_cleanup_pop
功能: 线程清理处理程序

头文件:
#include <pthread.h>
函数原形: void pthread_cleanup_push(void (*rtn)(void *)
void *arg);
                      void pthread_cleanup _pop(int execute);
参数: rtn 处理程序入口地址 arg 传递给处理函数的参数

返回值:

execute
:为0,不执行清理函数;非0,执行清理函数。

pthread_cleanup_pop removes the most recently installed cleanup handler. 
If the execute argument is not 0, it also executes the handler,by calling the 
routine function with arguments arg.   If the execute argument is 0, 
the handler is only removed but not executed.


线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序,线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。
要注意的是如果线程是通过从他的启动例程中返回而终止的,它的处理程序就不会调用。还要注意清理处理程序是按照与它们安装时相反的顺序调用的。

 

清理函数只在下述三种情况下执行:
1,
调用pthread_exit函数
2,
响应取消请求时
3,
调用参数非0pthread_cleanup_pop函数

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

void cleanup(void *arg)
{
    printf(“cleanup: %s/n”,(char *)arg);
}

void *thr_fn(void *arg) /*
线程入口地址*/
{
printf(“thread start/n”);
pthread_cleanup_push(cleanup,”thread first handler”);/*
设置第一个线程处理程序
*/
pthread_cleanup_push(cleanup,”thread second handler”); /*
设置第二个线程处理程序
*/
printf(“thread push complete/n”);
pthread_cleanup_pop(0); /*
取消第一个线程处理程序
*/
pthread_cleanup_pop(0); /*
取消第二个线程处理程序
*/
}

int main()
{
pthread_t tid;
void *tret;

pthread_create(&tid,NULL,thr_fn,(void *)1); /*
创建一个线程
*/
pthread_join(tid,&tret); /*
获得线程终止状态
*/
printf(“thread exit code %d/n”,(int)tret);
}

八、一次性初始化

      
有时候我们需要对一些posix变量只进行一次初始化,如线程键(我下面会讲到)。如果我们进行多次初始化程序就会出现错误。

      
在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为0,而任何依赖于初始化的代码都能测试该变量。如果变量值仍然为0,则它能实行初始化,然后将变量置为1。以后检查的代码将跳过初始化。

      
但是在多线程程序设计中,事情就变的复杂的多。如果多个线程并发地执行初始化序列代码,2个线程可能发现控制变量为0,并且都实行初始化,而该过程本该仅仅执行一次。初始化的状态必须由互斥量保护。

      
如果我们需要对一个posix变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始化进行控制。但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。

互斥量---变量静态初始化

pthread_once---变量动态初始化
函数9.
名称:
pthread_once
功能: 一次性初始化

头文件:
#include <pthread.h>
函数原形:
  pthread_once_t once_control=PTHREAD_ONCE_INIT;
                      int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));
参数: once_control         控制变量  init_routine   初始化函数

返回值: 若成功返回0,若失败返回错误编号。

类型为pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。

       pthread_once
函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到那个现成完成初始化返回。


下面就是该函数的程序例子:对互斥量的一次性初始化

#include <pthread.h>
#include <stdio.h>
pthread_once_t once=PTHREAD_ONCE_INIT;
pthread_mutex_t mutex;
 /*互斥量,我们后面会讲到
*/

void once_init_routine(void)
 /*一次初始化函数
*/
{
    int status;
status=pthread_mutex_init(&mutex,NULL);/*
初始化互斥量
*/
if(status==0)
    printf(“Init success!,My id is %u/n”,pthread_self());
}

void *child_thread(void *arg)
{
printf(“I’m child ,My id is %u/n”,pthread_self());
    pthread_once(&once,once_init_routine); /*
子线程调用一次性初始化函数
*/
}

int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;

pthread_create(&child_thread_id,NULL,child_thread,NULL);/*
创建子线程
*/
printf(“I’m father,my id is %u/n”,pthread_self());
pthread_once(&once,once_init_routine);/*
父线程调用一次性初始化函数
*/
pthread_join(child_thread_id,NULL);
}
程序运行结果如下:

./once
I’m father,My id is 3086874304
Init success!,My id is 3086874304
I’m child, My id is 3086871472    
从上面的结果可以看到当主函数初始化成功后,子函数初始化失败。

 

修改的例子,对全局变量的一次性初始化:

#include <pthread.h>

#include <stdio.h>

pthread_once_t once=PTHREAD_ONCE_INIT;

pthread_mutex_t mutex;

int flag;

 

void once_init_routine2(void)

{

    int status;

    flag=2;

    printf("flag=%d/n",flag);

}

 

void once_init_routine1(void)

{

    int status;

    flag=1;

    printf("flag=%d/n",flag);

}

 

void *child_thread(void *arg)

{

printf("I'm child ,My id is %u/n",pthread_self());

    pthread_once(&once,once_init_routine1); /*锟斤拷痰锟揭伙拷猿始锟?/

}

 

int main(int argc,char *argv[ ])

{

pthread_t child_thread_id;

 

pthread_create(&child_thread_id,NULL,child_thread,NULL);

printf("I'm father,my id is %u/n",pthread_self());

pthread_once(&once,once_init_routine2);

pthread_join(child_thread_id,NULL);

}程序运行结果如下:
./once
I’m child,My id is 3076438960
flag=1
I’m father, My id is 3076442336  


九、线程的私有数据
在进程内的所有线程共享相同的地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量,都可以被进程所有的线程读写。那怎样才能使线程拥有自己的私有数据呢。

 posix
提供了一种方法,创建线程键。线程键再与不同的线程私有数据地址进行关联。

线程键---线程的私有数据

函数10.
名称:
pthread_key_create
功能: 建立线程私有数据键

头文件:

#include <pthread.h>
函数原形:
int pthread_key_create(pthread_key *key,void(*destructor)(void *));
参数: key         私有数据键 destructor    清理函数

返回值: 若成功返回0,若失败返回错误编号。


第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数(清理函数),如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。


下面是程序例子:

#include <pthread.h>
#include <stdio.h>
pthread_key_t tsd_key;
pthread_once_t key_once=PTHREAD_ONCE_INIT;

void once_routine(void)
{
int status;

status=pthread_key_create(&tsd_key,NULL);/*
初始化线程私有数据键
*/
if(status=0)
    printf(“Key create success! My id is %u/n”,pthread_self());
}

void *child_thread(void *arg)
{
printf(“I’m child,My id is %u/n”,pthread_self());
pthread_once(&key_once,once_routine);/*
调用一次性初始化函数
*/
}

int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL);
printf(“I’m father,my id is %u/n”,pthread_self());
pthread_once(&key_once,once_routine);
}
程序运行结果如下:

I’m father,My id is 3086231232
Key create success! My id is 3086231232
I’m child,My id is 2086228400

 

智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值