Posix线程 它们那一大家子事儿,要觉得好你就收藏进被窝慢慢看(2)
605
2022-05-29
文章目录
①大神博客先推
②好,现在看我的
线程是啥玩意儿? 非要线程不可?
线程与进程千丝万缕的纠缠
线程间资源共享情况
⑴共享资源
⑵非共享资源
线程的缺点
③线程安全问题
这个模块他的博客里没
④哔哔完了不?放码过来!
创建线程
接下来演示线程安全:
获取当前线程id
判断俩线程是否相等
单次初始化
连接(Joining)和分离(Detaching)线程
又到了演示线程安全的时间了
⑤线程属性
⑥敲黑板:栈管理
准备好小板凳
未完待续···
①大神博客先推
我这人就是这么的无私,这位大神应该是退隐好几年了,但是留下了不少好东西。
Posix线程详解
不过我的小白文也有不少彩蛋哦↓↓↓
②好,现在看我的
毕竟大神的博客也太长了,而且深奥,大家就好了。
咱这个短,通俗易懂。
线程是啥玩意儿? 非要线程不可?
官方话就是:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1、提高程序的并发性 2、开销小,不需要重新分配内存 3、通信和共享数据方便
1
2
3
还真别说,大神就是厉害,这还有图有真相!!!
线程与进程千丝万缕的纠缠
(1)线程又被叫做轻量级进程,也有PCB,创建线程使用的底层函数和进程是一样的,都是clone。 (2)从内核里看线程和进程是一样的,都有各自不同的PCB,但是PCB指向的内存资源的三级页表是不同的。 (3)进程可以蜕变成线程,进程也可以说是主线程,就是高速路的主干道。 (4)在Linux下,线程是最小的执行单位,进程是最小的分配资源单位。
1
2
3
4
线程间资源共享情况
1、文件描述符表
2、每种信号的处理方式
3、当前工作目录
4、用户ID和组ID
5、内存地址空间
1、线程id
2、处理器现场和栈指针
3、独立的栈空间
4、errno变量
5、信号屏蔽字
6、调度优先级
线程的缺点
1、线程不稳定(这个是真的不稳定,后面章节会提) 2、线程调试困难(这个是真的头疼,难以调试的东西) 3、线程无法使用Unix经典事件,如信号
1
2
3
③线程安全问题
线程安全(Thread-safeness):
线程安全:简短的说,指程序可以同时执行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。
例如:假设你的程序创建了几个线程,每一个调用相同的库函数:
这个库函数存取/修改了一个全局结构或内存中的位置。
当每个线程调用这个函数时,可能同时去修改这个全局结构活内存位置。
如果函数没有使用同步机制去阻止数据破坏,这时,就不是线程安全的了。
这个模块他的博客里没
嘿嘿,如果看我的博客,那这就是一个彩蛋了。
我有☺☺☺可重入函数对于线程安全的意义(附函数表)
④哔哔完了不?放码过来!
创建线程
pthread_create
1
功能:创建一个线程
原语函数:
#include
1
2
3
参数释义:
thread:传递一个pthread_t变量进来,用以保存新线程的tid(线程id)
attr:线程属性设置,NULL代表使用默认属性(注(1))
(*start_routine)(void *):函数指针,指向新线程应该指向的函数模块
arg:老熟了,给前面那个函数传参用的,不传就写NULL
返回值:成功返回0.,失败返回错误号,错误号,错误号,前面说过errno不共享的。(线程里返回值统一这样的,后面不提了)
注(1):创建线程时,没什么特殊情况我们都是使用默认属性的,不过有时候需要做一些特殊处理,碧如调整优先级啊这些的。后面会说。
Q:怎样安全地向一个新创建的线程传递数据?
A:确保所传递的数据是线程安全的(不能被其他线程修改)。下面三个例子演示了那个应该和那个不应该。
我的代码太菜了,看那个大神的:
Example Code - Pthread Creation and Termination #include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 //下面的代码片段演示了如何向一个线程传递一个简单的整数。 //主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。 int *taskids[NUM_THREADS]; for(t=0; t 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //例子展示了用结构体向线程设置/传递参数。每个线程获得一个唯一的结构体实例。 struct thread_data{ int thread_id; int sum; char *message; }; struct thread_data thread_data_array[NUM_THREADS]; void *PrintHello(void *threadarg) { struct thread_data *my_data; ... my_data = (struct thread_data *) threadarg; taskid = my_data->thread_id; sum = my_data->sum; hello_msg = my_data->message; ... } int main (int argc, char *argv[]) { ... thread_data_array[t].thread_id = t; thread_data_array[t].sum = sum; thread_data_array[t].message = messages[t]; rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &thread_data_array[t]); ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 //例子演示了错误地传递参数。循环会在线程访问传递的参数前改变传递给线程的地址的内容。 int rc, t; for(t=0; t 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 获取当前线程id pthread_self 1 功能:获取调用线程tid(注(3)) 注(3):有的人就要问了,这东西不是都传出来了吗,直接打印不就完事儿了吗,为什么还要特地开一个函数去获取? 是这样的,线程id的类型是pthread_t,它在当前进程中是唯一的,但是在不同系统中这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,反正就是你猜不到的东西。 好,看原语: #include 1 2 3 判断俩线程是否相等 pthread_equal 1 功能:判断两个线程是否相等 原语: #include 1 2 3 注意这两个函数中的线程ID对象是不透明的,不是轻易能检查的。因为线程ID是不透明的对象,所以C语言的==操作符不能用于比较两个线程ID。 单次初始化 pthread_once (once_control, init_routine) pthread_once 在一个进程中仅执行一次init_routine。 任何线程第一次调用该函数会执行给定的init_routine,不带参数,任何后续调用都没有效果。 init_routine函数一般是初始化的程序 once_control参数是一个同步结构体,需要在调用pthread_once前初始化。 1 2 3 4 5 6 例如: pthread_once_t once_control = PTHREAD_ONCE_INIT; 1 连接(Joining)和分离(Detaching)线程 pthread_join(threadid,status) pthread_detach(threadid,status) pthread_attr_setdetachstate(attr,detachstate) pthread_attr_getdetachstate(attr,detachstate) 1 2 3 4 5 6 7 连接: “连接”是一种在线程间完成同步的方法。例如: > pthread_join()函数阻赛调用线程直到threadid所指定的线程终止。 > 如果在目标线程中调用pthread_exit(),程序员可以在主线程中获得目标线程的终止状态。 > > 连接线程只能用pthread_join()连接一次。若多次调用就会发生逻辑错误。 > > 两种同步方法,互斥量(mutexes)和条件变量(condition variables),稍后讨论。 1 2 3 4 5 6 可连接(Joinable or Not)? 当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。 只有是可连接的线程才能被连接(joined),若果创建的线程是分离的,则不能连接。 POSIX标准的最终草案指定了线程必须创建成可连接的。然而,并非所有实现都遵循此约定。 使用pthread_create()的attr参数可以显式的创建可连接或分离的线程 1 2 3 4 5 6 7 典型四步如下: 声明一个pthread_attr_t数据类型的线程属性变量 用 pthread_attr_init()初始化改属性变量 用pthread_attr_setdetachstate()设置可分离状态属性 完了后,用pthread_attr_destroy()释放属性所占用的库资源 1 2 3 4 5 6 7 分离(Detaching): pthread_detach()可以显式用于分离线程,尽管创建时是可连接的。 没有与pthread_detach()功能相反的函数 建议: 若线程需要连接,考虑创建时显式设置为可连接的。因为并非所有创建线程的实现都是将线程创建为可连接的。 若事先知道线程从不需要连接,考虑创建线程时将其设置为可分离状态。一些系统资源可能需要释放。 //这个例子演示了用Pthread join函数去等待线程终止。 //因为有些实现并不是默认创建线程是可连接状态,例子中显式地将其创建为可连接的。 #include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 ⑤线程属性 linux下线程属性是可以根据实际项目需要进行设置。 之前我们讨论的都是线程的默认属性,默认属性已经可以解决大部分线程开发时的需求。 如果需要更高的性能,就需要人为对线程属性进行配置。 typedef struct { int detachstate; //线程的分离状态 int schedpolicy; //线程的调度策略 struct sched schedparam;//线程的调度参数 int inheritsched; //线程的继承性 int scope; //线程的作用域 size_t guardsize; //线程栈末尾的警戒缓冲区大小 int stackaddr_set; //线程栈的设置 void* stackaddr; //线程栈的启始位置 size_t stacksize; //线程栈大小 }pthread_attr_t; //在上面我们可以看到,关于这个结构体中的相关参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。 线程属性设置的一般套路: 第一:定义属性变量并初始化 pthread_attr_t pthread_attr_init() 第二:调用你想设置的属性的接口函数 pthread_attr_setxxxxxxxx() 第三:创建线程的时候,第二个参数使用这个属性 第四:销毁属性 pthread_destroy(); 1 2 3 4 5 6 7 8 ⑥敲黑板:栈管理 防止栈问题: POSIX标准并没有指定线程栈的大小,依赖于实现并随实现变化。 很容易超出默认的栈大小,常见结果:程序终止或者数据损坏。 安全和可移植的程序应该不依赖于默认的栈限制,但是取而代之的是用pthread_attr_setstacksize分配足够的栈大小。 pthread_attr_getstackaddr和pthread_attr_setstackaddr函数可以被程序用于将栈设置在指定的内存区域。 pthread_attr_getstacksize (attr, stacksize) pthread_attr_setstacksize (attr, stacksize) pthread_attr_getstackaddr (attr, stackaddr) pthread_attr_setstackaddr (attr, stackaddr) 1 2 3 4 5 6 7 //这个例子演示了如何去查询和设定线程栈大小。 #include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 未完待续··· 想了想,线程同步模块和线程池放在下一个章节吧,我没有文章字数破W的好习惯☺☺ 任务调度
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。