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

网友投稿 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 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

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

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
相关文章