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

   日期:2020-05-11     浏览:108    评论:0    
核心提示:文章目录大神博客先推好,现在看我的线程是啥玩意儿? 非要线程不可?线程与进程千丝万缕的纠缠线程间资源共享情况共享资源非共享资源线程的缺点线程安全问题这个模块他的博客里没哔哔完了不?放码过来!创建线程接下来演示线程安全:获取当前线程id判断俩线程是否相等单次初始化连接(Joining)和分离(Detaching)线程又到了演示线程安全的时间了线程属性敲黑板:栈管理准备好小板凳未完待续···大神博客先推我这人就是这么的无私,这位大神应该是退隐好几年了,但是留下了不少好东西。Posix线程详解不过我的小操作系统

文章目录

    • ①大神博客先推
    • ②好,现在看我的
      • 线程是啥玩意儿? 非要线程不可?
      • 线程与进程千丝万缕的纠缠
      • 线程间资源共享情况
        • ⑴共享资源
        • ⑵非共享资源
      • 线程的缺点
    • ③线程安全问题
      • 这个模块他的博客里没
    • ④哔哔完了不?放码过来!
      • 创建线程
        • 接下来演示线程安全:
      • 获取当前线程id
      • 判断俩线程是否相等
      • 单次初始化
      • 连接(Joining)和分离(Detaching)线程
        • 又到了演示线程安全的时间了
    • ⑤线程属性
    • ⑥敲黑板:栈管理
        • 准备好小板凳
    • 未完待续···

①大神博客先推

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

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 %d\n", t); 

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

      if (rc){ 

         printf("ERROR; return code from pthread_create() is %d\n", 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 %d\n", 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 %d\n", 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 = %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; 

 

    

   pthread_attr_init(&attr); 

   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 

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

      printf("Creating thread %d\n", t); 

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

      if (rc) 
      { 

         printf("ERROR; return code from pthread_create()  

                is %d/n", rc); 

         exit(-1); 
      } 
   } 

 
    

   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 %d\n", rc); 
         exit(-1); 
      } 

      printf("Completed join with thread %d status= %ld\n",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的好习惯

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服