你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

你了解Linux多线程编程吗?

[复制链接]
gaosmile 发布时间:2020-10-10 17:49
1、线程与多线程的定义

线程存在于进程当中,是操作系统调度执行的最小单位。说通俗点线程就是干活,多线程也就是同时可以干不同的活而且还不会互相打扰,线程并没有自己的独立空间。

2、进程与线程的区别与联系

如果说进程是一个资源管家,负责从主人那里要资源的话,那么线程就是干活的苦力。一个管家必须完成一项工作,就需要最少一个苦力,也就是说,一个进程最少包含一个线程,也可以包含多个线程。苦力要干活,就需要依托于管家,所以说一个线程,必须属于某一个进程。进程有自己的地址空间,线程使用进程的地址空间,也就是说,进程里的资源,线程都是有权访问的,比如说堆啊,栈啊,静态存储区什么的。

线程就是个无产阶级,但无产阶级干活,总得有自己的劳动工具吧,这个劳动工具就是栈,线程有自己的栈,这个栈仍然是使用进程的地址空间,只是这块空间被线程标记为了栈。每个线程都会有自己私有的栈,这个栈是不可以被其他线程所访问的。

从上面我们知道了进程和线程区别,使用多线程首先是要和进程相对比,它是一种非常便捷的多任务操作方式;我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

线程操作相关的函数:

2.1、线程创建函数int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
2 F) A/ e! t5 a( u

参数说明:

pthread_create用于创建一个线程,成功返回0,否则返回Exxx(为正数)。

pthread_t tid:线程id的类型为pthread_t,通常为无符号整型,当调用pthread_create成功时,通过tid指针返回。

const pthread_attr_t *attr:指定创建线程的属性,如线程优先级、初始栈大小、是否为守护进程等。可以使用NULL来使用默认值,通常情况下我们都是使用默认值。

void *(*func) (void *):函数指针func,指定当新的线程创建之后,将执行的函数。

void *arg:线程将执行的函数的参数。如果想传递多个参数,请将它们封装在一个结构体中。

2.2、线程等待的函数" ?9 d7 n) s/ R: [
int pthread_join (pthread_t tid, void ** status);
- e% S" b/ f7 {1 B

参数说明:

pthread_join用于等待某个线程退出,成功返回0,否则返回Exxx(为正数)。

pthread_t tid:指定要等待的线程ID。

void ** status:如果不为NULL,那么线程的返回值存储在status指向的空间中(这就是为什么status是二级指针的原因!这种参数也称为“值-结果”参数)。

2.3、获得线程自身的ID的函数
0 k( r$ M. x' y% xpthread_t pthread_self (void);
/ V& o$ Z8 ^- Z3 N$ @

pthread_self用于返回当前线程的ID。

2.4、线程分离的函数
5 ~, k; V* i* a' `2 X% dint pthread_detach (pthread_t tid);
" S* r5 x! _8 G

pthread_detach用于指定线程变为分离状态,就像进程脱离终端而变为后台进程类似。成功返回0,否则返回Exxx(为正数)。变为分离状态的线程,如果线程退出,它的所有资源将全部释放。而如果不是分离状态,线程必须保留它的线程ID,退出状态直到其它线程对它调用了pthread_join。

2.5、退出线程(终止线程)的函数' o" e5 |4 q, i$ }$ g5 m! t
void pthread_exit (void *status);$ X8 h) j2 r! K, L% n

pthread_exit用于终止线程,可以指定返回值,以便其他线程通过pthread_join函数获取该线程的返回值。

参数说明:

void *status:指针线程终止的返回值。

Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,一定程度上弥补了这一缺陷。目前最为流行的线程机制LinuxThreads所采用的就是线程-进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。

线程编程实例:pthread.c

#include <stdio.h>1 H- G$ w( k8 g4 S# o/ |* H
#include <string.h>+ H: O6 i9 h! O
#include <stdlib.h>
. A# [) t: K. Y- m#include <unistd.h>
" ^6 l( t. I! L+ Q#include <pthread.h>
4 w0 O& ~8 [/ m$ G/ ~2 N
  }8 r. d8 Y9 Q# Wvoid *thread_1(void *arg)2 x( L% u& U& d) ?' s' G+ t
{0 l# X8 p% O7 J" c* v0 f& f
   printf("thread one\n");
1 X3 b5 c, ?7 k* V+ t3 k}5 h/ {, }& e) g! t
void *thread_2(void *arg)
. n' J2 B7 b: ^- t4 R; y* D{
5 C: K  H1 d. ^4 r   printf("thread two\n");5 P9 g9 J9 I7 T$ L
}$ c; h0 l5 ^* y; }+ o: d, G
; L% [& P; |" s2 R
int main()( ~& E# b) B. L3 ^6 E: C. t) {) `; B
{
& o9 S$ H5 o0 d) b    int ret = 0;
, T! e3 [/ ?6 ^# y* J    pthread_t pid = 0;
! a; u/ L, j* H$ J( H$ [! E    pthread_t pid1 = 0;
; v9 s$ e2 \+ C- \/ O% y  x9 @" B    //创建线程1
! b  p  H8 U0 x2 x    ret = pthread_create(&pid, NULL, thread_1, NULL);# e! q6 w6 G' q
    if(ret < 0)% c' x1 Z/ f8 ]
    {
* ~/ V% N9 z1 T' X9 j4 e      perror("pthread_create");
6 c$ m( \& @( N. G2 V/ s. m' _      exit(EXIT_FAILURE);
# i9 H! s; H$ R) a% _8 e8 Z: a    }
5 R7 W/ ]. U' R3 g/ d7 Q# \% _9 `    //创建线程2
, ?  E. p" {% T7 W3 d8 m    ret = pthread_create(&pid1, NULL, thread_2, NULL);: {4 r! w+ `8 O+ q- y
    if(ret < 0)0 S; M% j6 p. S) J
    {
5 P" ^/ ?$ |7 A% C7 R# ~& C      perror("pthread_create");2 x3 a, S. s. Z$ A9 K
      exit(EXIT_FAILURE);  I+ Y5 @8 ~) U2 m$ L4 ^1 @3 e
    }
" w, X% ?4 P% [( D6 L    //等待线程退出; K+ @- F1 i& N: a0 n* J7 h9 o. k5 k
    pthread_join(pid, NULL);
9 N8 F! \. Q: X3 r: Y" O+ I    pthread_join(pid1, NULL);
1 V4 v' D& j. i" i    return 0;
4 C. }6 v6 M/ j}
8 t* c. J( _$ A1 v3 c" n

注意,在gcc中,默认是不包含线程相关的库的,所以在编译这个程序操作如下是会产生错误的,如下图所示:

微信图片_20201010174817.png
正确的编译方式是下面这样,要加上-lpthread这个库,确保编译的时候链接上。如下图所示:
微信图片_20201010174820.png
运行结果:
微信图片_20201010174823.png

pthread.c创建了2个线程,并在线程中实现打印功能,最终调用pthread_join等待子线程运行结束,一并退出。

通过上面的一个例程会发现一个问题,当我们去操作一个文件的时候会出现一个问题,就是不知道该听谁的,这时候我们就需要一个互斥锁,让线程一个个来,a执行完之后在执行b。

pthread2.c

#include <stdio.h>/ x6 ]$ Y& k0 j) T9 ]3 G
#include <string.h>
1 u4 B# a; P: R0 E5 l$ B7 M#include <stdlib.h>
2 l6 F* u3 f: I6 H& Q  ]8 S#include <unistd.h>
3 P" V+ l/ w3 y1 {3 t! `" B0 R#include <pthread.h>
' p1 z% Z( d2 t/ P, b  Y/ O) @0 q7 Z. j3 i% Z- d
//定义一个互斥锁变量
! S; v4 o8 _) p) T4 b' O/ \5 cpthread_mutex_t m;6 j* ?- {0 P; A. s9 D7 f
void *thread_1(void *arg)
0 d4 u& @$ U6 L3 J2 R. t' w0 e{
# C5 h0 _; H  S  ]" Z    //互斥锁加锁
  ]; T/ e. o( m7 l    pthread_mutex_lock(&m);
' u% `- G& q7 o' p4 g5 C# u    printf("thread one\n");* A, P$ @6 m0 v( ~; }
    //互斥锁解锁
% }+ R2 U7 e2 F6 L3 m4 H% d0 }4 C    pthread_mutex_unlock(&m);4 R+ s" D5 D, {- l
}/ b; x) j6 }, e2 Y5 P. N
void *thread_2(void *arg)
1 D: a' o  U. v' J" \2 s' {9 w. S{
( n! \& L. A4 i    pthread_mutex_lock(&m);
# V: E! [9 \. h4 {& O    printf("thread two\n");' G9 m$ t# U& \& C7 U8 y2 N  Q" {
    pthread_mutex_unlock(&m);
1 f* v7 A8 Y5 t* D' S. i}1 A' N" D+ U. r0 P) ^& J. o& h

0 P" K9 ^0 D+ ^0 S* iint main()1 E( ~8 k7 t; O
{" \* x! w  i' i# s, d4 ]  s
    int ret = 0;% _1 o2 X2 V7 ]  g
    //以动态方式创建互斥锁
2 R* q" P+ W& B2 m    ret = pthread_mutex_init(&m, NULL);
0 p! _% n; ^2 r* i; ]/ T    if(ret < 0)/ T3 i! I  d& Z) o, G
    {! z& A+ c4 w- ~& d
        perror("pthread_mutex_init");
3 p% e/ \; o0 i8 j! H# Y7 a# o        exit(EXIT_FAILURE);
# S' W, x8 w+ S9 i9 B    }, w6 D" @4 s. J5 i- |
    pthread_t pid = 0;* x0 _/ k$ z  R& }1 d
    pthread_t pid1 = 0;
  d2 ~2 D$ D  O# q    ret = pthread_create(&pid, NULL, thread_1, NULL);" E6 z# d! B' O5 p4 ~5 U
    if(ret < 0)% X( C* g! P6 ^8 R8 j) T4 @
    {9 n! U* @/ }- J' a- A' R$ b
        perror("pthread_create");8 v- w5 [; F$ a  R
        exit(EXIT_FAILURE);: E$ D% T( [4 b
    }
6 C, E8 |0 I% e& m6 Z( P    ret = pthread_create(&pid1, NULL, thread_2, NULL);# a( B0 M* W0 F: f4 O. }
    if(ret < 0)
& U8 P' l: U  A& Y    {
! h# J( x9 \! }/ Q( t: a" m; c) \        perror("pthread_create");
9 p* v( O$ k' J. e1 z# f        exit(EXIT_FAILURE);" V7 q4 V6 j5 r: E
    }
( G# F. O3 [% M    pthread_join(pid, NULL);( m; e! h4 h5 T
    pthread_join(pid1, NULL);4 u  I* T, j. L# H- s4 t% p) \% R. f/ ?
    return 0;" L) R; n2 T0 A0 d5 s
}2 E4 e+ b$ {$ a, h$ f" z

一样的,同样执行编译加上-lpthread参数保证编译时链接线程库,然后运行,如下图所示:

微信图片_20201010174825.png
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该函数的某个数据时,进行保护,其它线程不能进行访问直到该线程读取完成,其它线程才可以使用。不会出现数据不一致或者数据污染。

/ q* G0 c( r' w% o9 }" L. `5 c( D/ L
赞 收藏 评论0 发布时间:2020-10-10 17:49

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版