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

时间:2022-07-23
本文章向大家介绍Posix线程 它们那一大家子事儿,要觉得好你就收藏进被窝里慢慢看 (1),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

①大神博客先推

我这人就是这么的无私,这位大神应该是退隐好几年了,但是留下了不少好东西。

Posix线程详解

不过我的小白文也有不少彩蛋哦↓↓↓


②好,现在看我的

毕竟大神的博客也太长了,而且深奥,大家收藏就好了。

咱这个短,通俗易懂。

线程是啥玩意儿? 非要线程不可?

官方话就是:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

1、提高程序的并发性
2、开销小,不需要重新分配内存
3、通信和共享数据方便

还真别说,大神就是厉害,这还有图有真相!!!

线程与进程千丝万缕的纠缠

(1)线程又被叫做轻量级进程,也有PCB,创建线程使用的底层函数和进程是一样的,都是clone。
(2)从内核里看线程和进程是一样的,都有各自不同的PCB,但是PCB指向的内存资源的三级页表是不同的。
(3)进程可以蜕变成线程,进程也可以说是主线程,就是高速路的主干道。
(4)在Linux下,线程是最小的执行单位,进程是最小的分配资源单位。

线程间资源共享情况

⑴共享资源

1、文件描述符表 2、每种信号的处理方式 3、当前工作目录 4、用户ID和组ID 5、内存地址空间

⑵非共享资源

1、线程id 2、处理器现场和栈指针 3、独立的栈空间 4、errno变量 5、信号屏蔽字 6、调度优先级

线程的缺点

1、线程不稳定(这个是真的不稳定,后面章节会提)
2、线程调试困难(这个是真的头疼,难以调试的东西)
3、线程无法使用Unix经典事件,如信号

③线程安全问题

线程安全(Thread-safeness):

线程安全:简短的说,指程序可以同时执行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。 例如:假设你的程序创建了几个线程,每一个调用相同的库函数: 这个库函数存取/修改了一个全局结构或内存中的位置。 当每个线程调用这个函数时,可能同时去修改这个全局结构活内存位置。 如果函数没有使用同步机制去阻止数据破坏,这时,就不是线程安全的了。

这个模块他的博客里没

嘿嘿,如果看我的博客,那这就是一个彩蛋了。 我有☺☺☺可重入函数对于线程安全的意义(附函数表)


④哔哔完了不?放码过来!

创建线程

pthread_create

功能:创建一个线程 原语函数:

#include<pthread.h>

int pthread_create(pthread_t *thread,const pthread_tattr_t *attr,void *(*start_routine)(void *),void *arg);

参数释义: 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 <pthread.h> 

#include <stdio.h> 

#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<NUM_THREADS; t++){ 

      printf("In main: creating thread %dn", t); 

      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t); 

      if (rc){ 

         printf("ERROR; return code from pthread_create() is %dn", rc); 

         exit(-1); 

      } 

   } 

   pthread_exit(NULL); 

} 

接下来演示线程安全:

//下面的代码片段演示了如何向一个线程传递一个简单的整数。
//主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。

int *taskids[NUM_THREADS]; 

for(t=0; t<NUM_THREADS; t++) 
{ 
   taskids[t] = (int *) malloc(sizeof(int)); 

   *taskids[t] = t; 

   printf("Creating thread %dn", t); 

   rc = pthread_create(&threads[t], NULL, PrintHello,  

        (void *) taskids[t]); 

   ... 

} 
//例子展示了用结构体向线程设置/传递参数。每个线程获得一个唯一的结构体实例。 

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]); 

   ... 
} 
//例子演示了错误地传递参数。循环会在线程访问传递的参数前改变传递给线程的地址的内容。 

int rc, t; 

for(t=0; t<NUM_THREADS; t++)  
{ 

   printf("Creating thread %dn", t); 

   rc = pthread_create(&threads[t], NULL, PrintHello,  

        (void *) &t); 

   ... 
} 

获取当前线程id

pthread_self

功能:获取调用线程tid(注(3))

注(3):有的人就要问了,这东西不是都传出来了吗,直接打印不就完事儿了吗,为什么还要特地开一个函数去获取? 是这样的,线程id的类型是pthread_t,它在当前进程中是唯一的,但是在不同系统中这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,反正就是你猜不到的东西。

好,看原语:

#include<pthread.h>

pthread_t pthread_self(void);

判断俩线程是否相等

pthread_equal

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

#include<pthread.h>

int pthread_self(pthread_t t1,pthread_t t2);

注意这两个函数中的线程ID对象是不透明的,不是轻易能检查的。因为线程ID是不透明的对象,所以C语言的==操作符不能用于比较两个线程ID。

单次初始化

pthread_once (once_control, init_routine)  

pthread_once 在一个进程中仅执行一次init_routine。
任何线程第一次调用该函数会执行给定的init_routine,不带参数,任何后续调用都没有效果。 
init_routine函数一般是初始化的程序 
once_control参数是一个同步结构体,需要在调用pthread_once前初始化。

例如:

pthread_once_t once_control = PTHREAD_ONCE_INIT;  

连接(Joining)和分离(Detaching)线程

pthread_join(threadid,status)  

pthread_detach(threadid,status)  

pthread_attr_setdetachstate(attr,detachstate)  

pthread_attr_getdetachstate(attr,detachstate)  

连接: “连接”是一种在线程间完成同步的方法。例如:

> pthread_join()函数阻赛调用线程直到threadid所指定的线程终止。  
> 如果在目标线程中调用pthread_exit(),程序员可以在主线程中获得目标线程的终止状态。 
> 
> 连接线程只能用pthread_join()连接一次。若多次调用就会发生逻辑错误。 
> 
> 两种同步方法,互斥量(mutexes)和条件变量(condition variables),稍后讨论。

可连接(Joinable or Not)?

当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。

只有是可连接的线程才能被连接(joined),若果创建的线程是分离的,则不能连接。 

POSIX标准的最终草案指定了线程必须创建成可连接的。然而,并非所有实现都遵循此约定。 

使用pthread_create()的attr参数可以显式的创建可连接或分离的线程

典型四步如下:

声明一个pthread_attr_t数据类型的线程属性变量 

用 pthread_attr_init()初始化改属性变量 

用pthread_attr_setdetachstate()设置可分离状态属性 

完了后,用pthread_attr_destroy()释放属性所占用的库资源 

分离(Detaching):

pthread_detach()可以显式用于分离线程,尽管创建时是可连接的。 没有与pthread_detach()功能相反的函数

建议:

若线程需要连接,考虑创建时显式设置为可连接的。因为并非所有创建线程的实现都是将线程创建为可连接的。 若事先知道线程从不需要连接,考虑创建线程时将其设置为可分离状态。一些系统资源可能需要释放。

又到了演示线程安全的时间了

//这个例子演示了用Pthread join函数去等待线程终止。
//因为有些实现并不是默认创建线程是可连接状态,例子中显式地将其创建为可连接的。

#include <pthread.h> 

#include <stdio.h> 

#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 = %en",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<NUM_THREADS; t++) 
   { 

      printf("Creating thread %dn", t); 

      rc = pthread_create(&thread[t], &attr, BusyWork, NULL);  

      if (rc) 
      { 

         printf("ERROR; return code from pthread_create()  

                is %d/n", rc); 

         exit(-1); 
      } 
   } 

 
   /* Free attribute and wait for the other threads */ 

   pthread_attr_destroy(&attr); 

   for(t=0; t<NUM_THREADS; t++) 
   { 
      rc = pthread_join(thread[t], &status); 

      if (rc) 
      { 
         printf("ERROR; return code from pthread_join() is %dn", rc); 
         exit(-1); 
      } 

      printf("Completed join with thread %d status= %ldn",t, (long)status); 
   } 

   pthread_exit(NULL); 

} 

⑤线程属性

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;

//在上面我们可以看到,关于这个结构体中的相关参数

默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

线程属性设置的一般套路:

 第一:定义属性变量并初始化  pthread_attr_t
                            pthread_attr_init()
 第二:调用你想设置的属性的接口函数
                            pthread_attr_setxxxxxxxx()
 第三:创建线程的时候,第二个参数使用这个属性
 
 第四:销毁属性
                            pthread_destroy();

⑥敲黑板:栈管理

防止栈问题:

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) 

准备好小板凳

//这个例子演示了如何去查询和设定线程栈大小。

#include <pthread.h> 

#include <stdio.h> 

#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<N; i++) 

     for (j=0; j<N; j++) 

      A[i][j] = ((i*j)/3.452) + (N-i); 

   pthread_exit(NULL); 

} 

  

int main(int argc, char *argv[]) 

{ 

   pthread_t threads[NTHREADS]; 

   size_t stacksize; 

   int rc, t; 

  

   pthread_attr_init(&attr); 

   pthread_attr_getstacksize (&attr, &stacksize); 

   printf("Default stack size = %li/n", stacksize); 

   stacksize = sizeof(double)*N*N+MEGEXTRA; 

   printf("Amount of stack needed per thread = %li/n",stacksize); 

   pthread_attr_setstacksize (&attr, stacksize); 

   printf("Creating threads with stack size = %li bytes/n",stacksize); 

   for(t=0; t<NTHREADS; t++){ 

      rc = pthread_create(&threads[t], &attr, dowork, (void *)t); 

      if (rc){ 

         printf("ERROR; return code from pthread_create() is %d/n", rc); 

         exit(-1); 

      } 

   } 

   printf("Created %d threads./n", t); 

   pthread_exit(NULL); 

} 

未完待续···

想了想,线程同步模块和线程池放在下一个章节吧,我没有文章字数破W的好习惯☺☺