线程

1.1什么是线程?

  在一个程序中的多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。

    要搞清楚fork系统调用和创建新线程之间的区别。当进程执行fork调用时,将创建出该进程的一份新的副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行(通常)

几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也拥有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。  

1.2第一个线程程序

  线程有一套完整的相关的函数库调用,它们绝大多数以pthread_开头。为了使用这些函数调用,我们必须定义宏_REENTRANT,在程序中包含头文件pthread.h,并且在编译程序时需要使用选项-lpthread来链接线程库。

在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。

为解决这个问题,需要使用可重入的例程。可重入代码可以被多次调用而仍然工作正常。编写的多线程程序,通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须出现于程序中的任何#include语句之前。

_REENTRANT为我们做三件事情,并且做的非常优雅:

(1)它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。

(2)stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。

(3)在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。

举个例子便于更好理解可重入的重要性:

int g_val = ;//定义一个全局变量

void add()
{
g_val++;
}

如果有n个线程调用该函数,g_val是一个全局变量,如果加上-D _REENTRANT,则可重入,即g_val不会受其他线程的影响;但是没加-D _REENTRANT的时候,n-1个线程调用该函数时变量g_val就会彼此影响,出现不可预料的结果。

不会影响的原因是,加-D _REENTRANT后虽然调用的还是这个函数,但是不同的是该机制自动把上面的函数变成了

void add_r()
{
g_val++;
}

调用的函数不同了。

创建新线程的函数:

#include <pthread.h>

int pthread_create(pthread_t  *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

第一个参数thread指向pthred_t类型数据的指针。线程被创建时,这个指针指向的变量中将被写入一个标识符,我们用该标识符来引用新线程。

第二个参数用于设置线程的属性。一般不需要特殊的属性,所以可以设为NULL。

第三个参数是一个函数地址,该函数以一个指向void的指针为参数,返回的也是一个指向void的指针。

用fork调用后,父子进程将在同一个位置继续执行下去,只是fork调用的返回值上不同的;

但对于新线程来说,我们必须明确地提供给它一个函数指针,新线程将在这个新位置开始执行。

第四个参数是要传递给上面函数(第三个参数)的参数。

该函数调用成功时返回值是0,如果失败则返回错误代码。

终止线程的函数:

#include <pthread.h>

void ptherad_exit(void *retval);

线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。该函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。

注意,绝对不能用它来返回一个指向局部变量的指针,因为线程在调用该函数后,这个局部变量就不再存在了,这将引起严重的程序漏洞。

等待线程的函数:

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

pthread_join函数在线程中的作用等价于进程中用来收集子进程信息的wait函数。

第一个参数指定了要等待的线程,线程通过pthread_create返回的标识符来指定。

第二个参数是一个指针,它指向了另一个指针,而后者指向了返回值。

这个函数成功返回0失败返回错误代码。

第一个线程示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *thread_function(void *arg);
char message[]="hello world!"; int main()
{
int res;
pthread_t a_thread;
void *thread_result; res = pthread_create(&a_thread,NULL,thread_function,(void *)&message);
if(res != )
{
perror("create thread is failed\n ");
exit(EXIT_FAILURE);
} printf("Waiting for thread to finish...\n");
res = pthread_join(a_thread,&thread_result);
if (res!=)
{
perror("thread join failed!\n");
exit(EXIT_FAILURE); } printf("Thread joined,it return %s\n",(char *)thread_result);
printf("message now is %s\n",message);
exit(EXIT_SUCCESS); } void *thread_function(void *arg)
{
printf("th_func is running,Aragement was %s\n",(char *)arg);
sleep();
strcpy(message,"bye");
pthread_exit("Thanks for the CPU time\n"); }

执行结果:

编译这个程序的时候,我们首先需要定义宏_REENTRANT。在少数系统上,可能还需要定义宏_POSIX_C_SOURCE。(然而我并没有加也能实现功能,待解决)

接下来必须链接正确的线程库。

我系统默认的线程库是NPTL(查看头文件/usr/include/pthread.h 如果显示版权日期在2003年或更晚,那基本你的Linux发行版使用的是NPTL实现)

使用如下命令编译和链接:

cc -D_REENTRANT thread1.c -o thread1 -lpthread

ps:我把cc 换成 gcc 编译通过。

分析:main函数在创建新线程成功后,继续执行。然后执行pthread_join函数,一直等到它指定的线程终止才返回。然后主线程打印新线程返回值和全局变量后结束。

而新创建的线程将会执行thread_function。先打印自己的参数,睡眠5S后,更改全局变量最后退出时向主线程返回一个字符串。

Linux学习5-线程的更多相关文章

  1. linux 学习干货

    学习了第七章. 每一个键盘对应一个信号.主要的有: ^代表 Ctrl <Backspance> erase ,删除一个字符. ^W werase,删除一个单词 ^U / ^X kill , ...

  2. Linux学习历程(持续更新整理中)

    1.文件目录操作命令 (1) ls   显示文件和目录列表 a ls -l  显示文件的详细信息 b ls -a 列出当前目录的所有文件,包含隐藏文件. c stat '目录/文件'   显示指定目录 ...

  3. Linux学习总结(十四)—— 查看CPU信息

    文章首发于[博客园-陈树义],点击跳转到原文Linux学习总结(十四)-- 查看CPU信息. Linux学习总结(十四)-- 查看CPU信息 商用服务器CPU最常用的是 Intel Xeon 系列,该 ...

  4. Linux多线程编程——线程的创建与退出

    POSIX线程标准:该标准定义了创建和操纵线程的一整套API.在类Unix操作系统(Unix.Linux.Mac OS X等)中,都使用Pthreads作为操作系统的线程.Windows操作系统也有其 ...

  5. Linux下查看线程数的几种方法汇总

    Linux下查看线程数的几种方法汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Linux下查看某个进程的线程数量 pstree命令以树状图显示进程间的关系(display ...

  6. Linux的进程线程及调度

    本文为宋宝华<Linux的进程.线程以及调度>学习笔记. 1 进程概念 1.1 进程与线程的定义 操作系统中的经典定义: 进程:资源分配单位. 线程:调度单位. 操作系统中用PCB(Pro ...

  7. [转帖]Linux的进程线程及调度

    Linux的进程线程及调度 本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10393707.html 本文为宋宝华<Linux的进程 ...

  8. 8)Linux程序设计入门--线程操作

    )Linux程序设计入门--线程操作 前言:Linux下线程的创建 介绍在Linux下线程的创建和基本的使用. Linux下的线程是一个非常复杂的问题,由 于我对线程的学习不时很好,我在这里只是简单的 ...

  9. Linux学习路线指南

    转载的,感觉写的挺好的,我自己知识复制了下,忘记了转载地址,抱歉! Linux学习路线指南 很多同学接触Linux不多,对Linux平台的开发更是一无所知.而现在的趋势越来越表明,作为一个优秀的软件开 ...

  10. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

随机推荐

  1. [转帖]go 的goroutine 以及 channel 的简介.

    进程,线程的概念在操作系统的书上已经有详细的介绍.进程是内存资源管理和cpu调度的执行单元.为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程里存在多个线程,这多个线程还是共享同一片内存空间 ...

  2. [转帖]Intel为何吊打AMD,先进半导体工艺带来什么?

    Intel为何吊打AMD,先进半导体工艺带来什么? 2016-3-10 15:38  |  作者:Strike   |  关键字:超能课堂,半导体工艺,CPU制程 分享到       按照摩尔定律的发 ...

  3. [转帖]Cgroups 与 Systemd

    Cgroups 与 Systemd 大神的文章很牛B .. https://www.cnblogs.com/sparkdev/p/9523194.html 看不太懂 , 转帖一下 自己留着好好看呢. ...

  4. isset与empty 的区别

    isset()与empty()函数的区别,isset()只需要验证一个值是否存在: 而empty()不但需验证这个值是否存在,还需检验它的值是否非空和非0: 注:isset()只检验一个变量是否已经设 ...

  5. Django 2.0 学习(22):Django CSRF

    Django CSRF CSRF攻击过程 攻击说明: 1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登陆网站A: 2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时 ...

  6. module.exports 、 exports 和 export 、 export default 、 import

    1:commonjs规范 module.exports={a:10,b:20} var test=require('lib/test') console.log(test.a);console.log ...

  7. linux中man 2与man 3区别

    1.Standard commands (标准命令)2.System calls (系统调用)3.Library functions (库函数)4.Special devices (设备说明)5.Fi ...

  8. CentOS 6.6搭建LNMP环境

    一.安装前 1.关闭linux的安全机制 vim /etc/selinux/config SELINUX=enforcing  改为  SELINUX=disabled 2.关闭iptables防火墙 ...

  9. HDU 6071 同余最短路 spfa

    Lazy Running Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)To ...

  10. SSO基于cas的登录

    概念介绍 1.定义 CAS ( CentralAuthentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法 ...