SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

1,概念:

  进程:一个正在执行的程序,他是资源分配的最小单位。进程中的事情语言按照一定顺序逐一进行

  线程:又称轻量级线程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,他是进程中一个实体,一个进程中可以有多个线程,这些线程共享进程的所有资                源,线程本身只包含一点必不可少的资源。

  并发:指在同一时刻,只能有执行一条指令,但多个线程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果 。   (看起来同时发生,针对单核

  并行:指在同一时刻,有多条指令在多个处理器上同时执行。 (真正的同时发生

  同步:彼此有依赖关系的调用不应该“同时发生”,而同步就是阻止那些“同时发生”的事情

  异步:与同步相对,任何两个彼此独立的操作时异步的,它表明事情独立发生

 

多线程的优势:

  1)在多处理器中开发程序的并行性

  2)在等待慢速IO操作时,程序可以执行其他操作,提高并发性

  3)模块化的编程,能更清晰的表达程序中独立事件的关系,结构清晰

  4)占用较少的系统资源

  多线程不一定要多处理器

 

线程创造 获取ID,生命周期
线程控制 终止、连接、取消、发送信号、清除操作
线程同步 互斥量、读写锁、条件变量
线程高级控制 一次性初始化、线程属性、同步属性、私有数据、安全的fork()

 

2,线程的基本控制

  创建新线程

    线程ID

  线程 进程
标识符类型 pthread_t pid_t
获取ID pthread_seif() getpid()
创建 pthread_create() fork()

      pthread_t : 结构体    unsigned long int   (linux 中提高移植性)        /usr/include/bits/pthreadtypes.h

    

       获取线程ID : pthread_self()

         头文件: #include <pthread.h>

         函数:    pthread_t  pthread_seif();

         返回值 :  线程ID

       编译链接时需要用到线程库  -pthread         ::gcc -pthread  (用到线程都要用)

 

    创建线程:ptnread_create()

        函数:  int  ptnread_create ( ptnread_t *restrict tidp, const ptnread_attr_t *restrict attr , void *(*start_routine)(void *) , void *restrict arg)

        参数:  第一个参数: 新线程的ID,如果成功则新线程的ID回填充到tipe 指向的内存

            第二个参数 : 线程属性 (调整策略,继承性,分离性...)

             第三个参数 : 回调函数  (新线程要执行的函数)

             第四个参数  : 回调函数的参数

        返回值 : 成功   0  ;  失败则返回错误码  

        编译时需要连接库  -pthread   主线程结束后必须有延时让其创建新线程,不然主进程直接被返回。方法用延时或者 下述2

 

Linux 多线程 Linux 第1张
 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <string.h>
 7 void print_id(char *s)
 8 {
 9    pid_t pid;
10    pthread_t tid;
11 
12    pid = getpid();
13    tid = pthread_self();
14 
15   printf("%s pid is %u,the tid is %lu\n",s,pid,tid);
16 
17 }
18 
19 void *thread_fun (void *arg)
20 {
21    print_id(arg);
22    return (void *)0;
23 }
24 
25 int main()
26 {
27    pthread_t ntid;
28    int err;
29 
30    err = pthread_create (&ntid, NULL , thread_fun ,"new thread");
31 
32    if(err!=0)
33    {
34         printf("create new thread failure\n");
35         return 0;
36     }
37 //      printf("create new thread sucess\n");
38         print_id("main thread:");
39         sleep(2);
40         return 0;
41 }
42 ~  
43 
44 结果:
45 main thread: pid is 2833,the tid is 3076491008
46 new thread pid is 2833,the tid is 3076488000
进程创建    

 

  线程的生命周期

  

      1,当C程序运行时,首先运行main()函数;在线程代码中,这个特殊的执行流被称为初始线程或者主线程。可以在初始线程中做任何普通线程可以做的事情

      2,主线程的特殊性在于,它在main() 函数返回的时候,会导致进程的结束,进程内所有的线程也将会结束。为了避免这种情况,需要在主线程中调用  pthread_exit() 函数,这样进程就会等所有线程结束时才终止。

      3,主线程接收参数的方式是通过argc 和argv[] ;而普通线程只有一个参数 void*

      4,绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以 延长,而普通线程的堆栈会收到限制,一旦溢出就会出错

 

      5,主线程是随着进程的创建而创建,其他线程可以通过调用函数来创建  ,主要为 pthread_create();

      6,新线程可能在当前线程从函数  pthread_cteate ()  返回之前就已经开始运行,甚至可能在返回之前就已经运行完毕

Linux 多线程 Linux 第3张
 1 #include "stdio.h"
 2 #include "pthread.h"
 3 #include "stdlib.h"
 4 #include "unistd.h"
 5 #include "sys/types.h"
 6 #include "string.h"
 7 
 8 struct student
 9 {
10    int age;
11    char name[20];
12    char id[5];
13 };
14 
15 void *thread(void *s)
16 {
17 
18    printf("student age is %d,name is %s,id is %s\n",((struct student *)s)->age,((struct student *)s)->name,((struct student *)s)->id);
19    return (void *)0;
20 
21 }
22 
23 
24 int main(int argc,char *argv[])
25 {
26 
27     pthread_t tid;
28     int err;
29 
30 
31      struct student stu;
32       stu.age =20;
33       memcpy(stu.name , "zhangsan" , 20);
34       memcpy(stu.id , "0001" , 5);
35     err = pthread_create(&tid , NULL , thread , (void *)(&stu));
36    if(err != 0)
37       {
38 
39         printf("creat new thread failure\n");
40         return 0;
41 
42         }
43     int i;
44         for(i=0;i<argc;i++)
45         {
46              printf("main thread args is %s\n",argv[i]);
47 
48         }
49         sleep(1);
50         return 0;
51 
52 }
53 ~ 
54 结果:
55 root@ubuntu:/home/xiaozhao# gcc -pthread b.c -o b
56 root@ubuntu:/home/xiaozhao# ./b 21  32 42
57 main thread args is ./b
58 main thread args is 21
59 main thread args is 32
60 main thread args is 42
61 student age is 20,name is zhangsan,id is 0001
验证主进程的特殊

 

Linux 多线程 Linux 第5张
 1 #include "stdio.h"
 2 #include "pthread.h"
 3 #include "stdlib.h"
 4 #include "unistd.h"
 5 #include "sys/types.h"
 6 #include "string.h"
 7 
 8 void *thread(void *s)
 9 {
10    int i=0;
11         while(1)
12 {
13    if(i%2 ==1)
14 
15    printf("new thread is %d\n",i);
16     i++;
17    sleep(1);
18 }
19    return (void *)0;
20 
21 }
22 
23 
24 int main(int argc,char *argv[])
25 {
26 
27     pthread_t tid;
28     int err;
29 
30     err = pthread_create(&tid , NULL , thread , "new thread");
31    if(err != 0)
32       {
33 
34         printf("creat new thread failure\n");
35         return 0;
36       }
37     int i=0;
38         while(i<10)
39         {
40         if(i%2 == 0)
41         printf( "main thread is %d\n",i);
42         i++;
43         sleep(1);
44         }
45         return 0;
46 
47 }
48 
49 
50 结果:
51 root@ubuntu:/home/xiaozhao# ./c
52 main thread is 0
53 new thread is 1
54 main thread is 2
55 new thread is 3
56 main thread is 4
57 new thread is 5
58 main thread is 6
59 new thread is 7
60 main thread is 8
61 new thread is 9
62 
63 
64 
65 
66 注意:
67  主线程循环结束,新线程也被结束;而新线程循环结束,不会影响主线程
68     
主线程和新线程交替打印奇偶

 

   

    线程的四个状态

         就绪: 当线程刚被创建处于就绪状态,或者当线程解除阻塞以后也会处于就绪状态。就绪的线程在等待一个可用的处理器,当一个运行的线程被抢占时,它立即又回到就绪状态

         运行 :线程正在运行,在多核系统中,可能同时又多个线程在运行

         阻塞: 线程在等待处理器意外的其他条件(试图加锁一个已经被锁住的互斥量,等待某个条件变量,调用singwait 等待尚未发生的信号,执行无法完成的I\O信号,由于内存错误)

            终止: 线程从启动函数中返回,或者调用 pthread_exit() 函数,或者被取消

 

    回收

           线程的分离属性:

          分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束的时,其所属的资源可以回收,一个没有被分离的线程在终止时会保留其虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸进程”。创建线程时默认时非分离的

          如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未被释放的系统资源和进程资源,包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等

          终止被分离的线程会释放所有的系统资源,但是必须释放有该线程占有的程序资源。由malloc() 或 mmap()  分配的内存可以在任何时候由任何线程释放,条件变量、互斥量、信号灯可以由任何线程销毁,只要他们被解锁了或者没有线程等待,但是只有互斥量的主人才能解锁它,所以在线程终止前,需要解锁互斥量

 

*********************************************************************************************************************************

 

  线程终止

    1, 如果进程中的任意一个线程调用了exit() , _Exit ,_exit , 那么整个进程就会终止

    2,不终止进程的退出方式:

      普通的单个线程有以下三种方式退出,不会终止进程:

  •   从启动历程中返回,返回值是线程的退出码    return
  •        线程可以被同一进程中的其他线程取消
  •        线程调用pthread_exit(void *rval) 函数,rval 是退出码
Linux 多线程 Linux 第7张
 1 #include "stdio.h"
 2 #include "pthread.h"
 3 #include "stdlib.h"
 4 #include "unistd.h"
 5 #include "sys/types.h"
 6 #include "string.h"
 7 
 8 void *thread_fun (void *s)
 9 {
10   if(strcmp("1",(char *)s)==0)
11              return (void *)1;
12 
13   if(strcmp("2",(char *)s)==0)
14                 pthread_exit((void *)2);
15 
16   if(strcmp("3",(char *)s)==0)
17                exit(3);
18 }
19 
20 
21 int main(int argc, char *argv[])
22 {
23      pthread_t tid;
24      int err;
25 
26      err = pthread_create (&tid , NULL , thread_fun , (void *)argv[1]);
27      if (err !=0)
28     {
29       printf("create new thread failure\n");
30        return 0;
31     }
32       sleep(1);
33       printf("my is main thread\n");
34       return 0;
35 
36 }
37 结果:
38 root@ubuntu:/home/xiaozhao# ./d 1
39 my is main thread
40 root@ubuntu:/home/xiaozhao# ./d 2
41 my is main thread
42 root@ubuntu:/home/xiaozhao# ./d 3
43 root@ubuntu:/home/xiaozhao# 
44 
45 
46 说明:exit()会直接导致进程退出,而前两种不会
三种退出方式

 

 

  线程连接(一个进程等待另一个进程完成在结束)

      int  pthread_join (pthread_t tid ,  void **rval)

  •       调用该函数的线程会一直阻塞,直到指定的线程tid 调用  pthread_exit () ; 从启动历程返回或者被取消
  •                       参数tid 就是指定线程的ID
  •                       参数ravl  是指定线程的返回码,如果线程被取消,那么ravl 被置为  PTHREAD_CANCELED
  •                       该函数调用成功会返回0,失败返回错误码

 

      调用pthread_join 会使指定的线程处于分离状态,如果指定线程已经处于分离状态,那么调用就会失败

      pthread_detach () 可以分离一个线程,线程可以自己分离自己

      int pthread_datach (pthread_t  thread);

        成功返回0 ;  失败返回错误码

 

Linux 多线程 Linux 第9张
 1 #include "apb.h"
 2 
 3 void *thread_fun1 (void *s)
 4 {
 5  printf("my is new thread1\n");
 6  return (void *)1;
 7 
 8 }
 9 
10 void *thread_fun2 (void *s)
11 {
12 
13  printf("my is new thread2\n");
14  pthread_detach(pthread_self());
15  pthread_exit((void *)2);
16 }
17 
18 int main()
19 {
20    int err1,err2;
21    pthread_t tid1,tid2;
22    void *rval1,*rval2;
23 
24   
25    err1 = pthread_create (&tid1 , NULL , thread_fun1 , NULL );
26    sleep(1);
27    err2 = pthread_create (&tid2 , NULL , thread_fun2 , NULL );
28    sleep(1);
29    if( err1 || err2 )
30         {
31           printf("create new thread failure\n");
32           return 0;
33         }
34 
35    printf("my is main thread\n");
36    printf("join rval1 is %d\n",pthread_join(tid1,&rval1));
37    printf("join rval2 is %d\n",pthread_join(tid2,&rval2));
38 
39    printf("thread1 exit is %d\n",(int )rval1);
40     printf("thread2 exit is %d\n",(int )rval2);
41 
42 
43 return 0;
44 }
45 ~   
46 
47 结果:
48 my is new thread1
49 my is new thread2
50 my is main thread
51 join rval1 is 0
52 join rval2 is 22
53 thread1 exit is 1
54 thread2 exit is -1216983040
55 
56 问题:
57 如果创建之后不加延时,线程二会比线程一早完成,输出早。。目前不知原因及解决办法
58 my is main thread
59 my is new thread2
60 my is new thread1
61 join rval1 is 0
62 join rval2 is 22
63 thread1 exit is 1
64 thread2 exit is -1216983040
连接,分离

 

 

     线程取消

    取消函数

      int  pthread_cancel (pthread_t tid);

      取消tid指定的线程,成功返回0.但是取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不意味着tid一定会终止。

 

    取消状态 

      取消状态就是线程对取消信号的处理方式,忽略或者响应。线程创建时默认响应取消信号

      int  pthread_setcancelstate (int state , int *oldstate);

        设置本线程对cancel 信号的反应,state有两种值 :  PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE。。分别表示收到信号后设为CANCLED状态和忽略CANCEL信号 继续运行;old_state 如果不为NULL ,则存入原来的CANCEL 状态以便恢复

 

    取消类型

      取消类型时线程对取消信号的响应方式,立即取消或者延时取消。线程创建时默认为延时取消

        int  pthread_setcanceltype (int type , int *oldtype);

      设置本线程取消动作的执行时机,type由两种取值: PTHREAD_CANCEL_DEFFERED   和   PTHREAD_CANCEL_ASYCHRONOUS  ,  仅当cancel  状态 为ENABLE时有效,分别表示收到信号后继续运行至下一个取消点再退出,和 立即执行取消动作 (退出);  oldtype  如果不为NULL ,则存入运来的取消动作类型值。

 

       取消点

      取消一个线程,他通常需要被取消线程的配合。线程在很多时候会查看自己是否由取消请求。如果有,就主动退出,这些查看是否有取消的地方成为取消点

         很多地方都是包含取消点,包括 pthread_join() , pthread_teacancel() , 等等大多数会阻塞的系统调用

Linux 多线程 Linux 第11张
  1 线程被取消
  2 #include "apb.h"
  3 
  4 void *thread_fun (void *s)
  5 {
  6 int stateval;
  7 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL);
  8 if (stateval != 0)
  9 {
 10   printf("set cancel state failure\n");
 11 }
 12 printf("my is new thread\n");
 13 sleep(4);
 14 
 15 printf("about to cancel\n");
 16 stateval  = pthread_setcancelstate (PTHREAD_CANCEL_ENABLE , NULL);
 17 printf("first cancel point\n");
 18 printf("secong cancel point\n");
 19 return (void *)20;
 20 
 21 
 22 }
 23 
 24 
 25 
 26 int main()
 27 {
 28   pthread_t tid;
 29    int err,cval;
 30    int jval;
 31    void *rval;
 32 
 33    err = pthread_create (&tid , NULL , thread_fun , NULL);
 34    if (err != 0)
 35     {
 36       printf("create new thread failure\n");
 37       return 0;
 38     }
 39     sleep(2);
 40     cval = pthread_cancel(tid);
 41    if(cval !=0)
 42     {
 43       printf("cancel thread failure\n");
 44     }
 45    jval = pthread_join(tid, &rval);
 46 
 47      printf("cancel rval is %d\n",(int )rval);
 48     return 0; 
 49 
 50 
 51 }
 52 
 53 结果:
 54 my is new thread
 55 about to cancel
 56 first cancel point
 57 cancel rval is -1
 58 
 59 
 60 线程忽略取消信号
 61 
 62 #include "apb.h"
 63 
 64 void *thread_fun (void *s)
 65 {
 66 int stateval;
 67 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL);
 68 if (stateval != 0)
 69 {
 70   printf("set cancel state failure\n");
 71 }
 72 printf("my is new thread\n");
 73 sleep(4);
 74 
 75 printf("about to cancel\n");
 76 stateval  = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL);
 77 printf("first cancel point\n");
 78 printf("secong cancel point\n");
 79 return (void *)20;
 80 
 81 
 82 }
 83 
 84 
 85 
 86 int main()
 87 {
 88   pthread_t tid;
 89    int err,cval;
 90    int jval;
 91    void *rval;
 92 
 93    err = pthread_create (&tid , NULL , thread_fun , NULL);
 94    if (err != 0)
 95     {
 96       printf("create new thread failure\n");
 97       return 0;
 98     }
 99     sleep(2);
100     cval = pthread_cancel(tid);
101    if(cval !=0)
102     {
103       printf("cancel thread failure\n");
104     }
105    jval = pthread_join(tid, &rval);
106 
107      printf("cancel rval is %d\n",(int )rval);
108     return 0; 
109 
110 
111 }
112 结果:
113 
114 my is new thread
115 about to cancel
116 first cancel point
117 secong cancel point
118 cancel rval is 20
119 
120 
121 线程接收到取消信号立即结束:
122 #include "apb.h"
123 
124 void *thread_fun (void *s)
125 {
126 int stateval,type;
127 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL);
128 if (stateval != 0)
129 {
130   printf("set cancel state failure\n");
131 }
132 printf("my is new thread\n");
133 sleep(4);
134 
135 printf("about to cancel\n");
136 stateval  = pthread_setcancelstate (PTHREAD_CANCEL_ENABLE , NULL);
137 type = pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
138 printf("first cancel point\n");
139 printf("secong cancel point\n");
140 return (void *)20;
141 
142 
143 }
144 
145 
146 
147 int main()
148 {
149   pthread_t tid;
150    int err,cval;
151    int jval;
152    void *rval;
153 
154    err = pthread_create (&tid , NULL , thread_fun , NULL);
155    if (err != 0)
156         {
157           printf("create new thread failure\n");
158           return 0;
159         }
160     sleep(2);
161     cval = pthread_cancel(tid);
162    if(cval !=0)
163         {
164           printf("cancel thread failure\n");
165         }
166    jval = pthread_join(tid, &rval);
167 
168      printf("cancel rval is %d\n",(int )rval);
169     return 0;
170 
171 
172 }
173 结果:
174 
175 my is new thread
176 about to cancel
177 cancel rval is -1
178        
取消函数,状态,类型

 

 

  向线程发送信号

        int  pthread_kill ( pthread_t thread , int sig );

      向指定ID的线程发送SIG信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程。(也就就是说,如果给一个线程发送SIGQUIT,但线程没有实现signal处理函数,则整个进程退出);  

      如果int sig 是0 , 为保留信号,其实并没有发送信号,只是用来判断线程是不是还活着

      成功返回0;失败返回出错码; 

 

   线程的信号:

        进程信号的处理:

          int  sigaction ( int signum ,const struct sigaction *act , struct sigaction *oldact);

          给信号signum设置一个处理函数,函数在sigaction 中指定

                            {

                                                                                                           atc.sa_mask      信号屏蔽字

                              act.as_handler  信号集处理程序

                                  }  

 

         int sigemptyset (sigset_t *set);        清空信号集

         int sigfillset (sigset_t *set);       将所有信号加入信号集

         int sigaddset (sigset_t *set , int signum);    增加一个信号到信号集

         int sigdelset (sigset_t *set , int signum); 删除一个信号到信号集

 

 

       多线程信号屏蔽处理

         int  pthread_sigmask (int how , const sigset_t *set , sigset_t *oidset);

          how  =  SIG_BLOCK  :  向当前信号掩码中添加set ,其中set 表示要阻塞的信号组   

          SIG_UNBLOCK         :  向当前信号掩码中删除set ,其中set 表示取消阻塞的信号组

          SIG_SETMASK    :  将当前信号掩码替换为set ,其中set 表示新的信号掩码。

        在多线程中,新线程的当前信号掩码会继承创造它的那个线程的信号掩码

        一般情况下,被阻塞的信号将不能中断词线程的执行,除非词信号的产生是因为程序出错

        SIGSEGV :  另外不能被忽略处理的信号     SIGKILL 和SIGSTOP 也无法被阻塞

 

             线程清除操作:

        线程可以安排它退出时的清除操作,这与进程的可以用 atexit() 函数安排进程退出时需要调用的函数类似。这样的函数称为线程清理程序。线程可以建立多个清除程序,处理程序记录在栈中,所以这些清理程序执行的顺序与他们注册程序的顺序相反

        pthread_cleanup_push ( void (*trn) (void *) , void *args)  //注册清理程序

        pthread_cleanup_pop  (int  excute)        //   清除清理程序

        这两个必须成对出现,否则编译无法通过

        

        当执行以下操作时,调用清理函数,清理函数的参数由args传入

  •   调用pthread_exit();
  •         响应取消请求
  •         用非零参数来调用pthread_cleanup_pop

        

Linux 多线程 Linux 第13张
 1   1 #include "apb.h"
 2   2 
 3   3 void *first_clean(void *s)
 4   4 {
 5   5 printf("%s first clean\n",(char *)s);
 6   6 return (void *)0;
 7   7 }
 8   8 
 9   9 void *second_clean(void *s)
10  10 {
11  11 printf("%s second clean\n",(char *)s);
12  12 return (void *)0;
13  13 }
14  14 
15  15 void *thread_fun1 (void *s)
16  16 {
17  17         printf("new thread1\n");
18  18         pthread_cleanup_push(first_clean,"thread1");
19  19         pthread_cleanup_push(second_clean,"thread1");
20  20 
21  21         pthread_cleanup_pop(1);
22  22         pthread_cleanup_pop(0);
23  23 
24  24         return (void *)1;
25  25 
26  26 }
27  27 
28  28 void *thread_fun2 (void *s)
29  29 {
30  30         printf("new thread2\n");
31  31         pthread_cleanup_push(first_clean,"thread2");
32  32         pthread_cleanup_push(second_clean,"thread2");
33  33 
34  34         pthread_cleanup_pop(1);
35  35         pthread_cleanup_pop(1);
36  36 
37  37         return (void *)1;
38  38 
39  39 }
40  40 
41  41 
42  42 int main()
43  43 {
44  44         pthread_t tid1,tid2;
45  45         int err;
46  46         void *rval1,*rval2;
47  47         err = pthread_create (&tid1 , NULL , thread_fun1 , NULL);
48  48         if(err != 0)
49  49         {
50  50                 printf("create new thread1 failure\n");
51  51                 return;
52  52 
53  53         }
54  54 
55  55         err = pthread_create (&tid2 , NULL , thread_fun2 , NULL);
56  56         if(err != 0)
57  57         {
58  58                 printf("create new thread2 failure\n");
59  59                 return;
60  60 
61  61         }
62            sleep(1);
63  65         return 0;
64  66 
65  67 
66  68 
67  69 }
68 ~        
69 结果:
70 
71 new thread2
72 thread2 second clean
73 thread2 first clean
74 new thread1
75 thread1 second clean
View Code

 

*线程的同步

    互斥量

  当多个线程共享相同的内存时,需要每一个线程看到相同的视图,当一个线程修改变量时,而其他线程也可以读取或者修改这个变量,就需要对这些线程进行同步,确保它们不会访问到无效的变量。

   互斥量的初始化和销毁:

      为了让线程访问数据不产生冲突,这就需要对变量加锁,使得同一时刻只有一个线程可以访问变量。互斥量本身就是锁,访问资源前对互斥量加锁,访问完成后解锁。

      当互斥量加锁后,其他所有需要访问该互斥量的线程都将阻塞

      当互斥量加锁以后,所有因为这个互斥量阻塞的线程都将变为就绪状态,第一个获得cpu 的线程会获得互斥量,变为运行态。而其他线程继续阻塞,在这种访问方式下,互斥量每次只有一个线程能向前执行

      

      互斥量用pthread_mutex_t 类型的数据表示,在使用前需要对互斥量初始化

  • 1,如果是动态分配的互斥量,可以调用 pthread_mutex_init() 函数初始化
  • 2,如果是静态非配的互斥量,还可以把他置为常量PTHREAD_MUTEX_INITIALIZER
  • 3,动态分配的互斥量在释放内存之前需要调用 pthread_mutex_destroy();

 

      int  pthread_mutex_init (pthread_mutex_t  *restrict mutex , const pthread_mutexattr_t  *restrict  attr);

        第一个参数时要初始化的互斥量,第二个参数是互斥量的属性,默认为NULL;

      int  pthread_mutex_destroy  ( pthread_mutex_t  *mutex) ;

        pthread_mutex_t  mutex  =   PTHREAD_MUTEX_INITIALIZER

     加锁 :  

       int  pthread_mutex_lock  ( pthread_mutex_t  *mutex);

        成功返回0 ,失败返回错误码。如果互斥量已经被锁住,则导致该线程阻塞

       int   pthread_mutex_trylock  ( pthread_mutex  *mutex);

           成功返回0 ,失败返回错误码。如果互斥量已经被锁住,不会该线程阻塞    

     

     解锁:

       int   pthread_mutex_unlock  ( pthread_mutex_t  *mutex);

        成功返回0 ;失败返回错误码

 

    

Linux 多线程 Linux 第15张
 1 #include "apb.h"
 2 
 3 struct student
 4 {
 5    int id;
 6    int age;
 7    int name;
 8     
 9 }stu;
10 
11 int i;
12 
13 pthread_mutex_t mutex;
14 
15 void *thread_fun1(void *arg)
16 {
17     while(1)
18     {
19         stu.id = i;
20         stu.age = i;
21         stu.name = i;
22         i++;
23         if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
24         {
25         printf("thread1 %d,%d,%d\n",stu.id,stu.age,stu.name);
26         break;
27         }
28     }
29     
30     return (void *)0;
31 }
32 
33 void *thread_fun2(void *arg)
34 {
35     while(1)
36     {
37         stu.id = i;
38         stu.age = i;
39         stu.name = i;
40         i++;
41         if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
42         {
43         printf("thread2%d,%d,%d\n",stu.id,stu.age,stu.name);
44         break;
45         }
46     }
47     
48     return (void *)0;
49 }
50 
51 int main()
52 {
53     pthread_t tid1,tid2;
54     int err;
55 
56     err = pthread_create (&tid1, NULL, thread_fun1, NULL);
57     if(err != 0)
58     {
59        printf("create new thread1 failure\n");
60        return 0;
61     }
62 
63     err = pthread_create (&tid2, NULL, thread_fun2, NULL);
64     if(err != 0)
65     {
66        printf("create new thread2 failure\n");
67        return 0;
68     }
69 
70     pthread_join(tid1, NULL);
71     pthread_join(tid2, NULL);
72     return 0;    
73 
74 }
75 
76 结果:
77 thread2646248,646248,646248
不加锁的问题

 

Linux 多线程 Linux 第17张
 1 #include "apb.h"
 2 
 3 struct student
 4 {
 5    int id;
 6    int age;
 7    int name;
 8     
 9 }stu;
10 
11 int i;
12 
13 pthread_mutex_t mutex;
14 
15 void *thread_fun1(void *arg)
16 {
17     while(1)
18     {
19         pthread_mutex_lock(&mutex);        
20         stu.id = i;
21         stu.age = i;
22         stu.name = i;
23         i++;
24         if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
25         {
26         printf("thread1 %d,%d,%d\n",stu.id,stu.age,stu.name);
27         break;
28         }
29         pthread_mutex_unlock(&mutex);
30     }
31     
32     return (void *)0;
33 }
34 
35 void *thread_fun2(void *arg)
36 {
37     pthread_mutex_lock(&mutex);
38     while(1)
39     {
40         stu.id = i;
41         stu.age = i;
42         stu.name = i;
43         i++;
44         if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name)
45         {
46         printf("thread2%d,%d,%d\n",stu.id,stu.age,stu.name);
47         break;
48         }
49     }
50     
51     pthread_mutex_unlock(&mutex);
52     return (void *)0;
53 }
54 
55 int main()
56 {
57     pthread_t tid1,tid2;
58     int err;
59     
60     err = pthread_mutex_init (&mutex , NULL);
61     if (err != 0)
62         {
63         printf("init mutex failure\n");    
64         return 0;
65         }
66     err = pthread_create (&tid1, NULL, thread_fun1, NULL);
67     if(err != 0)
68     {
69        printf("create new thread1 failure\n");
70        return 0;
71     }
72 
73     err = pthread_create (&tid2, NULL, thread_fun2, NULL);
74     if(err != 0)
75     {
76        printf("create new thread2 failure\n");
77        return 0;
78     }
79 
80     pthread_join(tid1, NULL);
81     pthread_join(tid2, NULL);
82     return 0;    
83 
84 }
加锁之后不会出现错乱

 

  

   读写锁

      读写锁与互斥量类似,不过读写锁由更高的并行性。不过读写锁有更高的并行性。互斥量要么加锁,要么不加锁,而且同一时刻值允许一个线程对其加锁,对于一个变量的读取,完全可以让多个线程同时进行操作。

      定义:   pthread_rwlock_t  rwlock;

      读写锁有三种状态,读模式下加锁,不加锁。一次只有一个线程可以占有写模式的读写锁,但多个线程科同时占有读模式的读写锁。

      读写锁在写加锁状态时,在它被锁之前,所有试图对这个锁加锁的线程都会被阻塞。读写锁在读加锁状态时,所有试图以读模式对其加锁的线程都会获得访问权,但如果线程希望以写模式对其加锁,它必须阻塞直到所有的线程释放。

      当读写锁——读模式加锁时,如果有线程试图以写模式对齐加锁,那么读写锁会阻塞随后的读模式锁请求,这样可以避免读锁长期占用,而写锁达不到请求

      读写锁非常适合对数据结构读次数大于写次数的程序,当它以读模式锁住时,时以共享的方式锁住的;当以写模式锁住时,是以独占模式锁住的

 

      初始化:

        int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);

      读模式加锁:

        int  pthread_rwlock_rdlock ( pthread_rwlock_t  *rwlock);

        int pthread_rwlock_tryrdlock (  pthread_rwlock_t  *rwlock);

      写模式加锁:

        int  pthread_rwlock_wrlock(  pthread_rwlock_t  *rwlock);

        int  pthread_rwlock_trywrlock(  pthread_rwlock_t  *rwlock);

      解锁:

        int  pthread_rwlock_unlock(  pthread_rwlock_t  *rwlock); 

         使用完需要销毁:
            int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

 

      成功返回0;

 

Linux 多线程 Linux 第19张
  1 读锁:共享关系
  2 #include "apb.h"
  3 
  4 int num = 0;
  5 pthread_rwlock_t rwlock;
  6 void *thread_fun1(void *s)
  7 {
  8     pthread_rwlock_rdlock(&rwlock);
  9     printf("thread1 printf num %d\n",num);
 10     sleep(5);
 11     printf("thread1 is over\n");
 12 
 13     pthread_rwlock_unlock(&rwlock);
 14     return (void *)1;
 15 }
 16 
 17 void *thread_fun2(void *s)
 18 {
 19     pthread_rwlock_rdlock(&rwlock);
 20     printf("thread2 printf num %d\n",num);
 21     sleep(5);
 22     printf("thread2 is over\n");
 23 
 24     pthread_rwlock_unlock(&rwlock);
 25     return (void *)1;
 26 }
 27 
 28 int main()
 29 {
 30     pthread_t tid1,tid2; 
 31     int err;
 32 
 33     err = pthread_rwlock_init(&rwlock,NULL);
 34      if(err != 0)
 35      {
 36         printf("rwlock init failure\n");
 37         return 0;
 38      }
 39 
 40     err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL);
 41      if(err != 0)
 42      {
 43         printf("create new thread1 failure\n");
 44         return 0;
 45      }
 46 
 47     err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL);
 48      if(err != 0)
 49      {
 50         printf("create new thread2 failure\n");
 51         return 0;
 52      }
 53     
 54     pthread_join(tid1,NULL);
 55     pthread_join(tid2,NULL);
 56 
 57     pthread_rwlock_destroy(&rwlock);
 58     return 0;
 59 
 60 }
 61 
 62 结果:
 63 thread1 printf num 0
 64 thread2 printf num 0
 65 (延时5s)
 66 thread2 is over
 67 thread1 is over
 68 
 69 写锁:
 70 #include "apb.h"
 71 
 72 int num = 0;
 73 pthread_rwlock_t rwlock;
 74 void *thread_fun1(void *s)
 75 {
 76     pthread_rwlock_wrlock(&rwlock);
 77     printf("thread1 printf num %d\n",num);
 78     sleep(5);
 79     printf("thread1 is over\n");
 80 
 81     pthread_rwlock_unlock(&rwlock);
 82     return (void *)1;
 83 }
 84 
 85 void *thread_fun2(void *s)
 86 {
 87     pthread_rwlock_rdlock(&rwlock);
 88     printf("thread2 printf num %d\n",num);
 89     sleep(5);
 90     printf("thread2 is over\n");
 91 
 92     pthread_rwlock_unlock(&rwlock);
 93     return (void *)1;
 94 }
 95 
 96 int main()
 97 {
 98     pthread_t tid1,tid2; 
 99     int err;
100 
101     err = pthread_rwlock_init(&rwlock,NULL);
102      if(err != 0)
103      {
104         printf("rwlock init failure\n");
105         return 0;
106      }
107 
108     err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL);
109      if(err != 0)
110      {
111         printf("create new thread1 failure\n");
112         return 0;
113      }
114 
115     err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL);
116      if(err != 0)
117      {
118         printf("create new thread2 failure\n");
119         return 0;
120      }
121     
122     pthread_join(tid1,NULL);
123     pthread_join(tid2,NULL);
124 
125     pthread_rwlock_destroy(&rwlock);
126     return 0;
127 
128 }
129 
130 结果:
131 thread2 printf num 0
132 (延时5s)
133 thread2 is over
134 thread1 printf num 0
135 (延时5s)
136 thread1 is over
读锁和写锁的不同

 

 

   条件变量

 

        当互斥量被锁住以后,发现当前线程还是无法完成自己的操作,那它应该释放互斥量,让其他线程工作。

    方式:(1,采用轮询的方式,不停的查询需要的条件 ;  2,让系统查询,使用条件变量  pthread_cond_t   cond  ;  )

        

       初始化:

          1,   pthread_cond_t  cond =  PTHREAD_COND_INITALIZER  ;

          2,   int  pthread_cond_init  (  pthread_cond_t  *restrict   cond  ,  const  pthread_condattr_t   *restrict  attr);      (初始量,属性)  默认属性为空  NULL

 

       销毁:

          int  pthread_cond_destroy  (  pthread_cond_t  *cond);

  

       使用:

          条件变量的使用需要配合互斥量

          int pthread_cond_wait  ( pthread_cond_t  *restrict  cond , pthread_mutex_t  *restrict mutex);

          1,使用pthread_cond_wait() 等待条件变为真。传递给pthread_cond_wait () 的互斥量对条件进行保护,调用者把锁住的互斥量传递给函数;

          2,这个函数将线程放到等待条件的线程列表上,然后对互斥量进行解锁,这是个原子操作。当条件满足时这个函数返回,返回后继续对互斥量加锁。

 

          int  pthread_cond_timewait  ( pthread_cond_t  *restrict cond , pthread_mutex_t  *restrict mutex , const struct timespec  *restrict abstime);

          3,  这个函数与pthread_cond_wait 类似 , 只是多一个timeout , 如果到了指定的时间条件还不满足,那么就返回,时间用下面结构体表示

           struct timespec {

              time_t  tv_sec;

              long  tv_nsec;

             };

           注意: 这个时间时绝对时间,例如等待3分钟,就要把当前时间加上3分钟后转换到timespec , 而不是直接将3分钟转化

 

          当条件满足的时候,需要唤醒等待条件的线程

            int  pthread_cond_broadcast  ( pthread_cond_t  *cond);

            int  pthread_cond_signal  ( pthread_cond_t  *cond);

            1,pthread_cond_broadcast   唤醒等待条件的所有线程

            2,pthread_cond_signal   至少唤醒等待条件的某一个线程

            注意: 一定要在条件改变后再唤醒线程

 

                                    Linux 多线程 Linux 第21张

Linux 多线程 Linux 第22张
  1 #include "apb.h"
  2 
  3 #define BUFFER_SIZE        5    //产品库存大小
  4 #define PRODUCT_CNT        30   //产品生产总数
  5 
  6 struct produte_cons
  7 {
  8     int buffer[BUFFER_SIZE];  //生产产品值
  9     pthread_mutex_t lock;      //互斥锁  volatile int 
 10     int readpos , writepos;   //读写位置
 11     pthread_cond_t  notempty; //条件变量 ,非空
 12     pthread_cond_t  notfull;  //非满
 13     
 14 }buffer;
 15 
 16 void init(struct produte_cons *p)
 17 {
 18 pthread_mutex_init( &p -> lock,NULL);    //互斥锁
 19 pthread_cond_init( &p -> notempty,NULL);//条件变量
 20 pthread_cond_init( &p -> notfull ,NULL);//条件变量
 21 p -> readpos  =  0;            //读写位置
 22 p -> writepos  =  0;
 23 
 24 }
 25 
 26 void finish(struct produte_cons *p)
 27 {
 28 pthread_mutex_destroy(&p -> lock);
 29 pthread_cond_destroy(&p -> notempty);
 30 pthread_cond_destroy(&p -> notfull);
 31 p -> readpos = 0;
 32 p -> writepos = 0;
 33     
 34 }
 35 
 36 
 37 //储存一个数据到buffer
 38 void put (struct produte_cons *p , int data) //输入产品子函数
 39 {
 40     pthread_mutex_lock (&p ->lock);
 41     if((p -> writepos+1)%BUFFER_SIZE== p->readpos)
 42     {
 43        printf("producer wait fir not full\n");
 44        pthread_cond_wait( &p -> notfull , &p -> lock);
 45 
 46   //这里,生产者 notfull 等待消费者 pthread_cond_signal(&p->notfull);信号
 47           //如果,消费者发送了 signal 信号,表示有了 空闲
 48     }
 49 
 50     p -> buffer[ p -> writepos] = data;
 51     p -> writepos ++;
 52     if (p -> writepos >= BUFFER_SIZE)
 53         p -> writepos = 0;
 54     pthread_cond_signal ( &p -> notempty);
 55     pthread_mutex_unlock (&p -> lock);
 56 }
 57 
 58 //读,移除一个数据从buffer
 59 int get(struct produte_cons *p)
 60 {
 61     int data;
 62 
 63     pthread_mutex_lock (&p -> lock);
 64     
 65     if(p ->readpos == p -> writepos)
 66     {
 67         printf("consumer wait for not empty\n");
 68         pthread_cond_wait (&p ->notempty , &p ->lock);
 69     }
 70 
 71     data = p ->buffer[p ->readpos];
 72     p ->readpos ++;
 73 
 74     if(p ->readpos >= BUFFER_SIZE)
 75         p ->readpos = 0;
 76     pthread_cond_signal (&p -> notfull);
 77     pthread_mutex_unlock (&p -> lock);
 78     
 79     return data;
 80 }
 81 
 82 void *producer(void *data) //子线程  ,生产
 83 {
 84    int n;
 85    for(n = 1;n<50; ++n)//生产50个产品
 86    {
 87        sleep(1);
 88        printf("put the %d produte ...\n",n);
 89        put(&buffer ,n);
 90        printf("put the %d produte succes\n",n);
 91        
 92    }
 93 
 94    printf("producer stopped\n");
 95    return NULL;
 96 }
 97 
 98 
 99 void *consumer (void *data)
100 {
101    static int cnt = 0;
102    int num;
103    while(1)
104     {
105     sleep(2);
106     printf("get product ...\n");
107     num = get(&buffer);
108     printf("get the %d product success\n",num);
109     if (++cnt == PRODUCT_CNT)
110         break;
111         
112     }
113    printf("consumer stopped\n");    
114    return NULL;
115     
116 }
117 
118 int main(int argc, char *argv[])
119 {
120     
121     pthread_t th_a,th_b;
122     void *retval;
123 
124     init(&buffer);
125 
126     pthread_create(&th_a , NULL , producer ,0);
127     pthread_create(&th_b , NULL , consumer ,0);
128     
129     pthread_join(th_a , &retval);
130     pthread_join(th_b , &retval);
131 
132     finish(&buffer);
133 
134     return 0;
135 }
136 
137 
138 结果:
139 
140 put the 1 produte ...
141 put the 1 produte succes
142 get product ...
143 get the 1 product success
144 put the 2 produte ...
145 put the 2 produte succes
146 put the 3 produte ...
147 put the 3 produte succes
148 get product ...
149 get the 2 product success
150 put the 4 produte ...
151 put the 4 produte succes
152 put the 5 produte ...
153 put the 5 produte succes
154 get product ...
155 get the 3 product success
156 put the 6 produte ...
157 put the 6 produte succes
158 put the 7 produte ...
159 put the 7 produte succes
160 get product ...
161 get the 4 product success
162 put the 8 produte ...
163 put the 8 produte succes
164 put the 9 produte ...
165 producer wait fir not full
166 get product ...
167 get the 5 product success
168 put the 9 produte succes
169 put the 10 produte ...
170 producer wait fir not full
171 get product ...
172 get the 6 product success
173 put the 10 produte succes
单生产者,单消费者

 

 

 

线程的高级属性:

  1)一次性初始化:

       通常当初始化应用程序时,可以比较容易地将其放在main()中。但当写一个库函数时,就不能在main() 函数中初始化了,可以用静态初始化,但使用一次性初始化会更简单

      首先定义一个pthread_once_t 的变量,这个变量要用宏PTHREAD_ONCE_INIT 初始化。然后创建一个与控制变量相关的初始化函数

      pthread_once_t once_control = PTHREAD_ONCE_INIT

      void init_routine()

      {

        //初始化互斥量

        //初始化读写锁

      }

 

      接下来就可以在任何时候调用pthread_once函数

      int pthread_once  ( pthread_once_t * once_control , void ( *init_routine)(void));      eg: pthread_once(&once , thread_fun1);

      功能:此函数使用初值为PTHREAD_ONCE_INIT 的once_control  变量  保证init_routine() 函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管 pthread_once()  调用会出现在多个线程中,  init_routine()  函数仅执行一次,在哪个线程中执行是由内核调度决定的。

 

      实际“一次性函数” 的执行状态由三种 :NEVER 从未(0)   ;  IN_PROGRESS  (1)正在执行   ;    DONE  完毕  (2)    .用once_control来表示pthread_once() 的执行状态:

        1) 如果once_control的初值为0 ,那么pthread_once 从未执行过,init_routine()函数会执行

     2)  如果once_control的初值为1,则由于所有pthread_once() 都必须等待其中一个激发“已执行一次”的信号,因此所有pthread_once()  都会陷入等待,init_control()就无法执行

     3)  如果once_control的初值为2,则表示pthread_once()  已执行过一次,从而所有pthread_once()  都会立即返回,init_control()就没有机会执行

 

      当pthread_once 的函数成功返回,once_control就会被设置为2.

 

 

****************************************************************************************************************************************************************************************************************************************************************

 

线程属性:

  1,线程的属性

    线程的属性用pthread_attr_t 类型结构表示,在创建线程的时候可以不用传入NULL,而是传入一个pthread_attr_t 结构,由用户自己配置线程的属性

    pthread_attr_t 类型对应用线程是不透明的,也就是说应用不需要了解有关属性对象内部结构的任何细节,因而可以增加线程的可移植性

 

    线程的属性:

名称 描述
detachstate 线程的分离状态
guardsize 线程栈末尾的警戒区域大小(字节数)
stacksize 线程栈的最低地址
stacksize 线程栈的大小(字节数)

      并不是所有的系统都支持线程的这些属性,因此需要检查当前系统是否支持;还有一些属性不包括在pthread_attr_t 内,如:线程的可取消状态,取消类型,并发度。

  

    线程属性初始化和销毁

      线程属性初始化:

            int  pthread_attr_init  ( pthread_attr_t  *attr);

      线程属性销毁:

            int  pthread_attr_destroy(  pthread_attr_t  *attr);

      如果在点用pthread_attr_init 初始化属性的时候分配了内存空间,那么pthread_attr_destroy() 将释放内存空间。除此之外,pthread_attr_destroy 还会用无效的值初始化pthread_dttr_t 对象,因此如果该属性对象呗误用,会导致创建线程失败。

            

  2,线程的分离属性:

       分离属性的使用方法:

      如果在创建线程的时候就知道不需要了解线程的终止状态,那么可以修改pthread_attr_t  结构体的detachstate 属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数来设置线程的分离状态属性。线程的分离属性由两种合法值:

          PTHREAD_CREATE_DETACHED    分离的

        PTHREAD_CREATE_JOINABLE      非分离的 ,可以连接

      int  pthread_attr_setdetachstate ( pthread_attr_t  *attr , int detachstate);

      int  pthread_attr_getdetachstate ( pthread_attr_t  *attr , int  *detachstate);          可获取线程的分离状态属性

 

      设置线程分离的步奏:

      1,定义线程属性变量  pthread_attr_t  attr

      2,初始化 attr,, pthread_attr_init  (&attr);

      3,设置线程为分离或非分离   pthread_attr_setdetachstate ( &attr , detachstate);

      4,创建线程pthread_create  ( &tid , &attr , thread_fun , NULL);

     所有的系统都会支持线程的分离状态属性。

 

Linux 多线程 Linux 第24张
 1 #include "apb.h"
 2 
 3 int num = 0;
 4 pthread_attr_t attr;
 5 void *thread_fun1(void *s)
 6 {
 7     printf("thread1 printf num %d\n",num);
 8     return (void *)1;
 9 }
10 
11 void *thread_fun2(void *s)
12 {
13     printf("thread2 printf num %d\n",num);
14     return (void *)2;
15 }
16 
17 int main()
18 {
19     pthread_t tid1,tid2;
20     int err;
21     
22     pthread_attr_init(&attr);
23     pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_JOINABLE);
24 //    pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED);
25 
26     err = pthread_create (&tid1, &attr ,thread_fun1 ,NULL);
27      if(err != 0)
28      {
29         printf("create new thread1 failure\n");
30         return 0;
31      }
32 
33     err = pthread_create (&tid2, &attr ,thread_fun2 ,NULL);
34      if(err != 0)
35      {
36         printf("create new thread2 failure\n");
37         return 0;
38      }
39     
40     pthread_join(tid1,NULL);
41     pthread_join(tid2,NULL);
42 
43     pthread_attr_destroy(&attr);
44 
45          return 0;
46 }
47 
48 结果:
49 thread2 printf num 0
50 thread1 printf num 0
51 
52 
53 如果用DETACHED   ,则join失败,新进程不会执行
分离的两种值

 

 

  3,线程栈属性:用ulimit -s 查看默认栈大小

     1、 线程的栈大小与地址;

        对于进程来说,虚拟地址空间的大小是固定的,进程中只有一个栈。因此它的大小通长不是问题。但对线程来说,同样的虚拟地址被所有的线程共享。如果应用程序使用了太多的线程,致使栈累计超过可用的虚拟地址空间,这个时候就需要减少线程默认栈的大小。另外,如果线程分配了大量的自动变量或者线程的栈帧太深,那么这个时候需要的栈要比默认的大。

 

        如果用完了虚拟地址空间,可以使用malloc  或者  mmap  来为其他栈分配空间,并修改栈的位置。

      修改栈属性:

        int  pthread_attr_setstack  (  pthread_attr_t  *attr , void  *stackaddr , size_t  stacksize);

      获取栈属性

        int  pthread_attr_getstack  (  pthread_attr_t  *attr , void  **stackaddr , size_t  *stacksize);

 

      参数:stackaddr  是栈的内存单元最低地址 , 参数  stacksize 是栈的大小。需要注意:  stackaddr 并不一定是栈的开始,对于一些处理器,栈的地址是从高往低,那么这时 stackaddr 时栈的结尾。

 

      当然也可以单独获取或者修改栈的大小,而不去修改栈的地址。对于栈大小设置,不能小于PTHREAD_STACK_MIN (需要头文件  limit.h)

      设置栈大小:

        int  pthread_attr_setstacksize ( pthread_attr_t  *attr , size_t  stacksize);

      获取栈大小:

          int  pthread_attr_getstacksize (  pthread_attr_t  *attr , aisze_t  *stacksize);

       对于栈大小的设置,在创建线程之后,还可以修改。

 

      对于遵循POSIX 标准的系统来说,不一定要支持线程的栈属性,需要检查

        1)在编译阶段使用

       _POSIX_THREAD_ATTR_STACKADDR   和   _POSIX_THREAD_ATTR_STACKSIZE  符号来检查系统是否支持线程栈属性

        2)在运行阶段

       _SC_THREAD_ATTR_STACKADD  和  _SC_THREAD_THREAD_ATTR_STACKSIZE  传给syconf () 函数检查系统对线程栈属性的支持

 

    2、 栈尾警戒区

      线程属性guardsize 控制着线程栈末尾以后用以避免栈溢出的扩展内存的大小,这个属性默认PAGESIZE 个字节 。可以把它设置为0 , 这样就不会提供警戒缓冲区。同样的,如果修改了stackaddr , 系统会认为用户管理栈,警戒缓存区会无效。

 

      设置guardsize:

        int  pthread_attr_setgusrdsize (  pthread_attr_t  *attr , size_t  guardsize);

      获取guardsize:

        int  pthread_attr_getguardsize (  pthread_attr_t  *attr  ,size_t  *guardsize);

 

Linux 多线程 Linux 第26张
 1 #include "apb.h"
 2 
 3 int num = 0;
 4 pthread_attr_t attr;
 5 void *thread_fun1(void *s)
 6 {
 7     size_t stacksize;
 8 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
 9     pthread_attr_getstacksize(&attr , &stacksize);
10     printf("thread1 init stack size is  %d\n",stacksize);
11     pthread_attr_setstacksize(&attr , 16400);
12         pthread_attr_getstacksize(&attr , &stacksize);
13     printf("thread1 after stack size is  %d\n",stacksize);
14 
15 #endif
16     return (void *)1;
17 }
18 
19 
20 int main()
21 {
22     pthread_t tid1;
23     int err;
24     
25     pthread_attr_init(&attr);
26     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
27 
28 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
29     pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
30 #endif
31     err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL);
32      if(err != 0)
33      {
34         printf("create new thread1 failure\n");
35         return 0;
36      }
37 
38     
39     pthread_join(tid1,NULL);
40     return 0;
41 }
42 
43 结果:
44 thread1 init stack size is  16384
45 thread1 after stack size is  16400
栈大小查看修改

 

 

  4,线程的同步属性:

    1)互斥量的属性:

       互斥量的属性用 pthread_mutexattr_t 类型数据表示,使用之前必须初始化,结束必须销毁:

      初始化:

        int  pthread_mutexattr_init (pthread_mutexattr_t  *attr);

      销毁:

        int  pthread_mutexattr_destroy ( pthread_mutexattr_t  *attr);

      

        1,进程共享属性

          共享进程属性由两种值:

        PTHREAD_PROCESS_PRIVATE      这个是默认值,同一个进程中的多个线程间同一个同步对象

        PTHREAD_PROCESS_SHARED      这个属性可以使互斥量在多个进程中同步进行,如果互斥量在多个进程的共享内存区域,那么具有这个属性的互斥量可以同步多进程

 

        设置互斥量进程共享属性

        int  pthread_mutexattr_getpshared  (  const  pthread_mutexattr_t  *restrict attr , int  *restrict pshared);

        int  pthread_mutexattr_setpshared  (  pthread_mutexattr_t  *attr , int pshared);

  

        进程共享属性需要检测系统是否支持,可以检测宏  _POSIX_THREAD_PROCESS_SHARED

        

        2,类型属性

互斥量类型 没有解锁时再次加锁 不占用时加锁 已解锁时加锁

PTHREAD_MUTEX_NORMAL  (默认类型)

死锁 未定义 未定义
PTHREAD_MUTEX_ERRORCHEK    (错误检查) 返回错误 返回错误 返回错误
PTHREAD_MUTEX_RECURSIVE    (递归) 允许 返回错误 返回错误
PTHREAD_MUTEX_DEFAULT  (默认,请求系统设置为其他三种) 未定义 未定义 未定义

 

            设置互斥量的类型属性:

              int  pthread_mutexattr_gettype (  const pthread_mutexattr_t  *attr , int  *restrict  type);

              int  pthread_mutexattr_settype (  pthread_mutexattr_t  *attr , int  type);

 

Linux 多线程 Linux 第28张
 1 #include "apb.h"
 2 
 3 int main()
 4 {
 5     char *shm = "myshm";
 6     char *shm1 = "myshm1";
 7     int shm_id,shm_id1;
 8     char *buf;
 9     pid_t pid;
10 
11     //打开共享内存
12     shm_id = shm_open(shm , O_RDWR|O_CREAT , 0644);
13     //调整共享内存大小
14     ftruncate(shm_id ,100);
15     //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程
16     buf = (char *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id ,0);
17 
18     pid = fork();
19     if(pid == 0)
20     {
21         //休眠一秒让父进程先运行
22         sleep(1);    
23         printf("i am child proccess\n");
24 
25         //将共享内存内容改为hello
26         memcpy (buf , "hello" , 6);
27         printf("child buf is : %s\n",buf);
28         
29     }
30     else if (pid >0)
31     {
32         printf("i an parent proccess\n");    
33         
34         //修改内容为world
35         memcpy(buf , "world",6);
36         sleep(3);
37         printf("parent buf is : %s\n",buf);
38     }
39     
40     //解除映射
41     munmap(buf , 100);
42     //消除共享内存
43     shm_unlink(shm);
44     return 0;
45 }
46 
47 gcc a.c -lrt -o a
48 
49 
50 结果:
51 i an parent proccess
52 i am child proccess
53 child buf is : hello
54 parent buf is : hello
View Code

 

Linux 多线程 Linux 第30张
 1 #include "apb.h"
 2 int main()
 3 {
 4     char *shm = "myshm";
 5     char *shm1 = "myshm1";
 6     int shm_id,shm_id1;
 7     char *buf;
 8     pid_t pid;
 9     
10     pthread_mutex_t *mutex;
11     pthread_mutexattr_t mutexattr;
12 
13     //打开共享内存
14     shm_id1 = shm_open(shm1 , O_RDWR|O_CREAT , 0644);
15     //调整共享内存大小
16     ftruncate(shm_id1 ,100);
17     //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程
18     mutex = (pthread_mutex_t *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id1 ,0);
19     
20     pthread_mutexattr_init(&mutexattr);
21 #ifdef _POSIX_THREAD_PROCESS_SHARED
22     pthread_mutexattr_setpshared(&mutexattr , PTHREAD_PROCESS_SHARED);
23 #endif
24     pthread_mutex_init(mutex, &mutexattr);
25     //打开共享内存
26     shm_id = shm_open(shm , O_RDWR|O_CREAT , 0644);
27     //调整共享内存大小
28     ftruncate(shm_id ,100);
29     //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程
30     buf = (char *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id ,0);
31 
32     pid = fork();
33     if(pid == 0)
34     {
35         //休眠一秒让父进程先运行
36         sleep(1);    
37         printf("i am child proccess\n");
38 
39         pthread_mutex_lock(mutex);
40         //将共享内存内容改为hello
41         memcpy (buf , "hello" , 6);
42         printf("child buf is : %s\n",buf);
43         pthread_mutex_unlock(mutex);
44     }
45     else if (pid >0)
46     {
47         printf("i an parent proccess\n");    
48         
49         pthread_mutex_lock(mutex);
50         //修改内容为world
51         memcpy(buf , "world",6);
52         sleep(3);
53         printf("parent buf is : %s\n",buf);
54         pthread_mutex_unlock(mutex);
55     }
56     
57 
58     pthread_mutexattr_destroy(&mutexattr);
59     pthread_mutex_destroy(mutex);
60 
61     munmap(buf,100);
62     shm_unlink(shm1);
63     //解除映射
64     munmap(buf , 100);
65     //消除共享内存
66     shm_unlink(shm);
67     return 0;
68 }
69 
70 
71 gcc a.c -lrt -pthread -o a
72 
73 
74 
75 结果:
76 i an parent proccess
77 i am child proccess
78 parent buf is : world
79 child buf is : hello
View Code

 

      2)读写锁

          只有一个进程共享属性

          初始化:

           int  pthread_rwlockattr_init(  pthread_rwlockattr_t  *attr);

          销毁:

           int  pthread_rwlockattr_destroy (  pthread_rwlockattr_t  *attr);

          设置读写锁的进程共享属性:

           int  pthread_rwlockattr_getpshared (  const pthread_rwlockattr_t  *restrict attr , int  *restrict pshared);

           int  pthread_rwlockattr_setpshared (  pthread_rwlock_t  *attr , int  pshared);

 

      3)条件变量

          也有进程共享属性

          初始化:

           int  pthread_condattr_init(  pthread_condattr_t  *attr);

          销毁:

           int  pthread_condattr_destroy (  pthread_condattr_t  *attr);

          设置读写锁的进程共享属性:

           int  pthread_condattr_getpshared (  const  pthread_condattr_t  *restrict attr , int  *restrict pshared);

           int  pthread_condattr_setpshared (  pthread_rwlock_t  *attr , int  pshared);

 

    5,线程的私有数据

        应用程序设计中有必要提供一种变里,使得多个函数多个线程都可以访问这个变量( 看起来是个全局变里),但是线程对这个变里的访问都不会彼此产生影响(貌似不是全局变里哦〉,但是你需要这样的数据,比如errno。那么这种数据就是线程的私有数据,尽管名字相同,但是每个线程访问的都是数据的副本。

        在使用私有数据之前,首先要创建一个与私有数据相关的键,来获取对私有数据的访问权限。这个间哦的类型是  pthread_key_t

 

        int  pthread_key_create ( pthread_key_t  *key , void  (*destructor)(void *)  );

 

        创建的键放在key指向的内存单元里,destructor 是与键相关的析构函数 。当线程调用 pthread_exit  或者  使用return  返回,析构函数就会被调用。当析构函数调用的时候,它只有一个参数,这个参数是与key关联的那个数据的地址,因此可以在析构函数中将这个数据销毁。  键使用完也可以销毁,与它关联的数据并没有销毁

 

        int  pthread_key_delete (  pthread_key_t  key);

 

        有了这个键之后,就可以将私有数据和键关联起来,这样就可以通过键来找到数据。所有的线程都可以访问这个键,但他们可以为键关联不同的数据(名字一样,值却不同的全局变量)

 

        int  pthread_setspecific  (  pthread_key_t  key , const  void  *value);        将私有数据与键关联

 

        void  *   pthread_getspecific (  pthread_key_t  key);               获取私有数据的地址,如果没有数据与key 关联,那么返回空

 

Linux 多线程 Linux 第32张
 1 #include "apb.h"
 2 
 3 pthread_key_t  key;
 4 
 5 void *thread_fun1(void *s)
 6 {
 7     printf("thread1 start\n");
 8     int a =1;
 9     pthread_setspecific(key , (void *)a);
10     sleep(2);
11     printf("thread1 key -> data is %d\n", (int)pthread_getspecific(key));
12     return (void *)1;
13 }
14 
15 void *thread_fun2(void *s)
16 {
17     sleep(1);
18     printf("thread2 start\n");
19     int a =2;
20     pthread_setspecific(key , (void *)a);
21     
22     printf("thread2 key -> data is %d\n",(int) pthread_getspecific(key));
23 }
24 
25 int main()
26 {
27     pthread_t tid1,tid2;
28     int err;
29     
30     pthread_key_create(&key,NULL);
31     err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL);
32      if(err != 0)
33      {
34         printf("create new thread1 failure\n");
35         return 0;
36      }
37 
38     err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL);
39      if(err != 0)
40      {
41         printf("create new thread2 failure\n");
42         return 0;
43      }
44     
45     pthread_join(tid1,NULL);
46     pthread_join(tid2,NULL);
47 
48     pthread_key_delete(key);
49 }
50 结果:
51 thread1 start
52 thread2 start
53 thread2 key -> data is 2
54 thread1 key -> data is 1
私有化

 

 

    6,线程与fork()

        

       当线程调用fork函数时,就为子进程创建了整个进程地址空间的副本,子进程通过继承整个地址空间的副本,也会将父进程的互斥量、读写锁、条件变量的状态继承过来。也就是说,如果父进程中互斥量是锁着的,那么在子进程中互斥量也是锁着的(尽管子进程自己还没有来得及lock),这是丰常不安全的,因为不是子进程自己锁住的,它无法解锁。

 

          子进程内部只有一个线程,由父进程中调用fork函数的线程副本构成。如果调用fork的线程将互斥量锁住,那么子进程会拷贝一个pthread_ mutex_ lock副本,这样子进程就有机会去解锁了。或者互斥量根本就没被加锁,这样也是可以的,但是你不能确保永远是这样的情况。

          pthread_ atfork函数给你创造了这样的条件,它会注册三个函数

 

          int  pthread_ atfork  (void (*prepare) (void)  ,   void (*parent) (void)  ,   void (*child) (void)  );

 

          prepare是在fork调用之前会被调用的,parent在fork返回父进程之前调用,child在fork返回子进程之前调用。如果在prepare中加锁所有的互斥量,在parent和child中解锁所有的互斥量,那么在fork返回之后,互斥量的状态就是未加锁。

          

      可以有多个pthread_ atfork 函数,这是也就会有多个处理程序( prepare, parent, child) 。多个prepare的执行顺序与注册顺序相反,而parent和child的执行顺序与注册顺序相同。

 

  

Linux 多线程 Linux 第34张
 1 #include "apb.h"
 2 pthread_mutex_t mutex  =  PTHREAD_MUTEX_INITIALIZER;
 3 
 4 void *thread_fun(void *s)
 5 {
 6    sleep(1);
 7    pid_t pid;
 8 
 9    pid = fork();
10    if(pid==0)
11    {
12     pthread_mutex_lock(&mutex);
13     printf("child process\n");
14     pthread_mutex_unlock(&mutex);
15        
16    }
17    if(pid>0)
18    {
19     pthread_mutex_lock(&mutex);
20     printf("parent process\n");
21     pthread_mutex_unlock(&mutex);
22    }
23 }
24 
25 int main()
26 {
27    pthread_t tid;
28 
29    if(pthread_create(&tid , NULL , thread_fun,NULL))
30    {
31     printf("create thread failure\n");
32     return;
33        
34    }
35    pthread_mutex_lock(&mutex);
36    sleep(2);
37    printf("main\n");
38    pthread_mutex_unlock(&mutex);
39    pthread_join(tid ,NULL);
40 
41    pthread_mutex_destroy(&mutex);
42    return 0;
43     
44     
45 }
46 
47 
48 结果:
49 main
50 parent process
51 
52   PID TTY          TIME CMD
53  2375 pts/0    00:00:00 su
54  2376 pts/0    00:00:00 bash
55  2720 pts/0    00:00:00 vi
56  3036 pts/0    00:00:00 c
57  3047 pts/0    00:00:00 ps
58 
59 c 进程阻塞
子进程继承加锁,阻塞 Linux 多线程 Linux 第36张
 1 #include "apb.h"
 2 pthread_mutex_t mutex  =  PTHREAD_MUTEX_INITIALIZER;
 3 
 4 void *thread_fun(void *s)
 5 {
 6    sleep(1);
 7    pid_t pid;
 8 
 9    pthread_mutex_lock(&mutex);
10    pid = fork();
11    if(pid==0)
12    {
13 //    pthread_mutex_lock(&mutex);
14 //    printf("child process\n");
15     pthread_mutex_unlock(&mutex);
16     printf("child process\n");
17        
18    }
19    if(pid>0)
20    {
21 //    pthread_mutex_lock(&mutex);
22 //    printf("parent process\n");
23     pthread_mutex_unlock(&mutex);
24     printf("parent process\n");
25    }
26 }
27 
28 int main()
29 {
30    pthread_t tid;
31 
32    if(pthread_create(&tid , NULL , thread_fun,NULL))
33    {
34     printf("create thread failure\n");
35     return;
36        
37    }
38   // pthread_mutex_lock(&mutex);
39   // sleep(2);
40    printf("main\n");
41   // pthread_mutex_unlock(&mutex);
42    pthread_join(tid ,NULL);
43 
44 //   pthread_mutex_destroy(&mutex);
45    return 0;
46     
47     
48 }
49 
50 结果:
51 main
52 parent process
53 child process
在新线程中加锁fock之前,父子进程拷贝加锁,与其加锁可以配对 Linux 多线程 Linux 第38张
 1 #include "apb.h"
 2 pthread_mutex_t mutex  =  PTHREAD_MUTEX_INITIALIZER;
 3 
 4 void prepare()
 5 {
 6      pthread_mutex_lock(&mutex);
 7     printf("i am prepare\n");
 8 }
 9 
10 void parent()
11 {
12    pthread_mutex_unlock(&mutex);
13    printf("i am parent\n");
14     
15 }
16 
17 void child()
18 {
19     pthread_mutex_unlock(&mutex);
20     printf("i am child\n");
21     
22 }
23 void *thread_fun(void *s)
24 {
25    sleep(1);
26    pid_t pid;
27    pthread_atfork(prepare , parent ,child);
28    pid = fork();
29    if(pid==0)
30    {
31     pthread_mutex_lock(&mutex);
32     printf("child process\n");
33     pthread_mutex_unlock(&mutex);
34        
35    }
36    if(pid>0)
37    {
38     pthread_mutex_lock(&mutex);
39     printf("parent process\n");
40     pthread_mutex_unlock(&mutex);
41    }
42 }
43 
44 int main()
45 {
46    pthread_t tid;
47 
48    if(pthread_create(&tid , NULL , thread_fun,NULL))
49    {
50     printf("create thread failure\n");
51     return;
52        
53    }
54   // pthread_mutex_lock(&mutex);
55   // sleep(2);
56    printf("main\n");
57   // pthread_mutex_unlock(&mutex);
58    pthread_join(tid ,NULL);
59 
60 //   pthread_mutex_destroy(&mutex);
61    return 0;
62     
63     
64 }
65 
66 
67 结果:
68 
69 main
70 i am prepare
71 i am parent
72 parent process
73 i am child
74 child process
采用pthread_atfork函数

 

 

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄