Posix线程 它们那一大家子事儿,要觉得好你就收藏进被窝里慢慢看 (1)

网友投稿 570 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、每种信号的处理方式

Posix线程 它们那一大家子事儿,要觉得好你就收藏进被窝里慢慢看 (1)

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 int pthread_create(pthread_t *thread,const pthread_tattr_t *attr,void *(*start_routine)(void *),void *arg);

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 #include #define NUM_THREADS 5 void *PrintHello(void *threadid) { int tid; tid = (int)threadid; printf("Hello World! It's me, thread #%d!\n", tid); pthread_exit(NULL); } int main (int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int rc, t; for(t=0; 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

//下面的代码片段演示了如何向一个线程传递一个简单的整数。 //主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。 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 pthread_t pthread_self(void);

1

2

3

判断俩线程是否相等

pthread_equal

1

功能:判断两个线程是否相等

原语:

#include int pthread_self(pthread_t t1,pthread_t t2);

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 #include #define NUM_THREADS 3 void *BusyWork(void *null) { int i; double result=0.0; for (i=0; i<1000000; i++) { result = result + (double)random(); } printf("result = %e\n",result); pthread_exit((void *) 0); } int main (int argc, char *argv[]) { pthread_t thread[NUM_THREADS]; pthread_attr_t attr; int rc, t; void *status; /* Initialize and set thread detached attribute */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(t=0; 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

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 #include #define NTHREADS 4 #define N 1000 #define MEGEXTRA 1000000 pthread_attr_t attr; void *dowork(void *threadid) { double A[N][N]; int i,j,tid; size_t mystacksize; tid = (int)threadid; pthread_attr_getstacksize (&attr, &mystacksize); printf("Thread %d: stack size = %li bytes /n", tid, mystacksize); for (i=0; i

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小时内删除侵权内容。

上一篇:【设计模式】享元模式 实现 ( 实现流程 | 抽象享元类 | 具体享元类 | 享元工厂 | 用户调用 | 代码模板 )
下一篇:在Windows-IDEA调试Spark的 Master、Worker、Executor、Application、Shell
相关文章