12 posix 线程

问题? 用fork调用来创建新的进程代价太高。
如果能让一个进程同时做两件事情或至少看起来是这样将会非常有用。而且,你可能有两件更多的事情以一种非常紧密的方式同时发生。这就是需要线程发挥租用的时候了。

大纲:
》在进程中创建新线程;
》在一个进程中同步线程之间的数据访问;
》修改线程的属性;
》在同一个进程中,从一个线程控制另外一个线程。

12.1什么是线程##

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


弄清楚fork系统调用和创建线程之间的区别很重要;
==》 当进程使用fork进行调用时,将创建该进程的一份新的副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行(通常)几乎完全独立于父进程。当在进程中创建一个新进程时,新的执行线程将拥有自己的栈(因此有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。
PS:一般我们用fork来创建一个进程的时候,这个进程中就只有一个线程(可能在ios上就是主线程(ios上应该是经过处理和限制过的))。

对于单核CPU,线程的同时执行只是一个聪明但非常有效的幻觉。

Linux的线程实现版本和POSIX标准之间还是存在着细微的差别,最明显的是关于信号处理部分,这些差别中的大部分收到底层Linux内核的限制,而不是函数库实现所强加的。
PS: 信号量是和linux以及UNIX的内核有关的;

优化linux对线程的主持:增强linux线程的性能和删除一些不需要的限制,其中大部分的工作都是集中在“如何将用户级的线程映射到内核级的线程”。
这些项目中有两个重要的项目:
下一代POSIX线程(New Generation POSIX Thread,简写为NGPT)
本地POSIX线程库(Native POSIX Thread Library 简写:NPTL).
都是通过修改linux上的内容进行修改支持新的函数库;
后来重心放在NPTL,所以NPTL 这个将会成为下一代标准。

线程的优点和缺点##

(虽然linux在创建进程方面的效率也很高)
优点:
1)有时,让程序看起来好像是在同时运行两件事情是很有用的。

eg:
(1)在编辑文档的同时对文档中的单词个数进行实时统计。
这个是后一个线程负责处理用户的输入并执行文本编辑工作,另外一个(它也可以看到相同的文档内容)则不断刷新单词计数变量……甚至还有第三个线程。
第一个线程通过这个共享的技术变量让用户随时了解自己的工作进展的情况。
(2)一个多线程的数据库服务,这是一种明显的单进程服务多用户的情况。它会响应一些请求的同时阻塞另外一些请求,使之等待磁盘操作,从而改善整体上的数据吞吐量。对数据库来说,这个明显的多任务工作如果用多进程的方式俩完成将很艰难做到高效,因为各个不同的进程必须紧密合作才能满足加锁和数据一致性方面,而用多线程来完成就比用更多进程要容易多。

2)一个混杂着输入、计算和输出的应用程序,可以将这几个部分分离为3个线程来执行,从而改善程序执行的性能。
当输入或输出线程等待连接时,另外一个线程可以继续执行。因此,如果一个进程在任一时刻最多只能够一件事情的话,线程可以让它在等待连接之列的事情的同时做一些其他有用的事情。一个需要同时处理多个网络连接的服务器应用程序也是一个天生适用于多线程的例子。

3)线程之间切换需要操作系统做的工作要比进程之间的切换少得多,因此多个线程对资源的需求要远小于多个进程。如果一个程序在逻辑上需要有多个执行线程,那么在单处理器系统上把它运行为一个多线程程序才更符合实际情况。

缺点:
1)编写多线程程序需要仔细设计。
(多线程程序中,因时序山的细微偏差或无意造成的变量共享而引发错误的可能性很大)
2)对多线程的程序的调试要比单线程程序的调试困难得多,因为线程之间的交互非常难以控制。
3)将大量计算分成两个部分,并把这两个部分作为两个不同的线程来运行的程序在一台单处理器机器上并不一定运行的更快,除非计算确实允许它的不同部分可以被同时计算,而且运行它的机器拥有多个处理器来支持真正的多处理。

第一个线程程序##

第一个线程代码

运行结果

线程函数在头文件<pthread.h>中,一般都是以pthread_开头。
并且在编译程序的时候需要用到选项-lpthread来链接线程库(可以下面 的编译指令);

在最初设计UNIX /POSIX库历程时,我们假设的是每个进程只有一个可执行线程。一个明显的例子就是errno,该变量就是用来用于获取某个变量失败之后的错误信息。在一个多线程里面,默认只有一个errno的变量供所有的变量共享。造成了一个线程在获取刚才的错误信息的时候,很容就被其他先线程所修改。类似的还有fputs之类的函数,这个函数通常是用一个全局性区域来缓存输出数据。
PS:一个进程只有一个变量共享给该进程的多个线程使用,就造成该变量值的改变。

这个问题应该怎么样修改?可以在不同的线程创建不同的errno。这个进程没有什么区别?? 局部变量的相对于线程而不是进程。
解决方案:需要使用被称为可重入的例程。可重入代码可以被多次调用而仍然正常工作,这些调用可以来自不同的线程以及也可以来自不同的嵌套调用。所以,代码中可重入的部分通常只使用局部变量,这使得该代码的调用都会获得唯一一份数据副本。
【这个应该就是前面所说的使用局部变量来处理每一个线程都有一份副本】

&& 宏定义的内容是
编写多线程时,需要定义_REENTRANT来告诉编译器我们需要进行可重入功能,这个宏的定义必须位于程序中的任何#include 语句之前。 它将为我们做3件事情,并且做得非常优雅,以至于我们一般不需要知道它到底做了哪些事。
1)它会对部分函数重新定义它们的可安全重入的版本,这些函数的名字一般不会发生改变,只是会在函数名后面台添加_r字符串。例如:函数名字gethostbyname将变为gethostbyname_r。
2)stdio.h 中原来宏的形式实现的一些函数将变成安全重入的函数;
3)在errno.h 中定义的变量errno现在将成为一个函数调用,它能够以一种多线程安全的方式来获取真正的errno值。
在程序中包含头文件pthread.h 还将向我们提供一些其他的将在代码中使用到的定义和函数原型,就如同头文件stdio.h 为标准输入和标准输出例程所提供的定义一样。
最后,需要确保在程序中包含了正确的线程头文件,并且在编译器程序时链接了实现pthread函数的正确的线程库。

PS: 基本上明白了多线程函数的使用以及调用的

上面函数的解析
创建一个线程(类似于进程中的fork() 创建进程)

int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
                   const pthread_attr_t * _Nullable __restrict,
                   void * _Nullable (* _Nonnull)(void * _Nullable),
                   void * _Nullable __restrict);

// 第一个参数: 指向pthread_t 类型数据的指针,线程创建的时候,这个指针指向的变量中将被写入一个标示符,应用这个标示符来引用新的线程
// 第二个参数: 用于设置线程属性 (一般不需要特殊的属性,所以这里设置为NULL)
// 第三个参数:告诉线程将要启动执行的函数 (调用了thread_function)
// 第四个参数:传递给第三个参数函数的的参数(就是传给thread_function这个函数的参数)
void * (*)(void *)
第三个参数的内容要求:我们必须传递一个函数地址,该函数以一个指向void的指针为参数,返回的也是一个指向void的指针为参数,返回的也是一个指向void的指针。
【用fork调用后,父子进程将在同意而位置继续执行下去,只是fork调用返回值是不同的,
&&&&&&
但是对于新线程来说,我们必须明确提供给他一个函数指针,新线程将在这个新位置开始执行。】
创建线程的函数若是成功就会返回0,若是失败就会返回错误代码。

pthread_create和大多数pthread_系列函数一样,在失败的时并没有遵循UNIX函数的惯例返回-1,这种情况在UNIX函数中属于一少部分。

void pthread_exit(void *retval)
线程通过调用pthread_exit函数终止执行,就如同进程在结束调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。注意,绝不能够用它来返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不在存在了,这将引起严重的程序漏洞。

int pthread_join(pthread_t th, void **thread_return);
这个参数指定了将要等待的线程,线程通过pthread_create返回的标识符来指定。第二个参数是一个指针,它指向另外一个指针,而后者指向线程的返回值。与pthread_create类似,这个函数在成功时返回0,失败的时候返回错误码。

基本步骤:
(1)编译这个程序的时候,我们首先定义宏定义_REENTRANT.在少数系统上,可能还需要定义宏:_POSIX_C_SOURCE ,但一般不需要定义它。
(2)接下来必须链接真确的线程库。(如实使用老的Linux版本,默认的线程库不是NPTL,需要升级linux)简单的检查方法是查看头文件/usr/include/pthread.h 。 如果这个文件中显示的版本日期是2003年或更晚,那几乎可以肯定你的Linux发型版使用的是NPTL实现。
(3)在验证并安装了正确的文件后,就可以进行编译。


编译命令

(4)运行这个程序可以看到结果(在mac上运行也是可以的(xcode))

实验解析:
(1)定义了在创建线程时需要由它调用的一个函数的原型:
void *thread_function(void *arg);
(2)根据pthread_create的要求,它只有一个指向void的指针作为参数,返回的也是指向void的指针。
(3)main函数中,首先定义几个变量,然后调用pthread_create后面的代码,而新线程开始执行thread_function函数。
pthread_t a_thread;
void thread_result;
res = pthread_create(&athread,NULL,thread_function,(void
)message);
我们向pthread_create函数传递了一个pthread_t类型对象的地址,今后可以用它来引用这个新线程。我们不想改变默认的线程属性,所以设置第二个参数为NULL。最后两个参数分别为将要调用的函数和一个传递给该函数的参数。
如果调用成功了,就会有两个线程在运行。原先的线程(main)继续执行pthread_create后面的代码,而新线程开始执行thread_result函数。
原先的线程在查明新线程已经启动后,将调用pthread_join函数,
res = pthread_join(a_thread,&thread_result);
PS: 这个方法 才是 执行线程的方法,第二个参数一定钥匙全局变量。
我们给该函数传递两个参数,一个是正在等待其结束的线程的标示符,休眠一会儿,然后更新全局变量,最后退出并向主线程返回一个字符串。新线程修改了数组message,而原先的线程也可以访问该数组。如果我们调用fork而不是pthread_create,就不会有这样的效果了。

12.4 同时执行##

首先明白概念: 单cpu使用的是“轮询技术”;程序仍然利用这一事实,即除局部变量外,所有其他变量都将在一个进程中的所有线程之间共享。

修改的代码

主线程中 // 如果run_now 的值是1,就打印1并设置为2,否则就稍做休息然后再检查它的值。我们不断的检查来等待它的值变为1,这个种方式被称为“忙等待”,虽然已经在两次检查之间休息1秒钟来减慢检查的频率。在本章的后面我们将看到对这个问题的一个更好的解决方法。
新线程中
在新的线程执行的thread_function函数中,我们所做的事情和上面的大部分相同,只是把run_now 的值颠倒一下。

运行结果

实验解析:
每个线程通过设置run_now变量的方法来通知另外一个线程开始运行,然后,它会等待另一个线程改变了这个变量的值后再次运行。这个例子显示了两个线程之间自动交替执行,同时也再次阐明了一个观点,即这两个线程共享run_now 变量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容