linux线程浅析

在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例,
它并不执行什么, 只是维护应用程序所需的各种资源.
而线程则是真正的执行实体. 为了让进程完成一定的工作,
进程必须至少包含一个线程.

进程所维护的是程序所包含的资源(静态资源), 如: 地址空间,
打开的文件句柄集, 文件系统状态, 信号处理handler,
等;线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息,
待处理的信号集, 等;

然而, 一直以来, linux内核并没有线程的概念.
每一个执行实体都是一个task_struct结构, 通常称之为进程.
进程是一个执行单元, 维护着执行相关的动态资源. 同时,
它又引用着程序所需的静态资源(注意这里说的是linux中的进程).通过系统调用clone创建子进程时,
可以有选择性地让子进程共享父进程所引用的资源.
这样的子进程通常称为轻量级进程.

linux上的线程就是基于轻量级进程,
由用户态的pthread库实现的.使用pthread以后, 在用户看来,
每一个task_struct就对应一个线程,
而一组线程以及它们所共同引用的一组资源就是一个进程.

但是, 一组线程并不仅仅是引用同一组资源就够了,
它们还必须被视为一个整体.对此, POSIX标准提出了如下要求:

  • (1)
    查看进程列表的时候,
    相关的一组task_struct应当被展现为列表中的一个节点;
  • (2)
    发送给这个”进程”的信号(对应kill系统调用),
    将被对应的这一组task_struct所共享, 并且被其中的任意一个”线程”处理;
  • (3)
    发送给某个”线程”的信号(对应pthread_kill),
    将只被对应的一个task_struct接收, 并且由它自己来处理;
  • (4)
    当”进程”被停止或继续时(对应SIGSTOP/SIGCONT信号),
    对应的这一组task_struct状态将改变;
  • (5)
    当”进程”收到一个致命信号(比如由于段错误收到SIGSEGV信号),
    对应的这一组task_struct将全部退出;
  • (6)
    等等(以上可能不够全);

1.
Linuxthreads

在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib.
linuxthreads利用前面提到的轻量级进程来实现线程,
但是对于POSIX提出的那些要求, linuxthreads除了第5点以外,
都没有实现(实际上是无能为力):

  • (1)
    如果运行了A程序, A程序创建了10个线程,
    那么在shell下执行ps命令时将看到11个A进程, 而不是1个(注意,
    也不是10个, 下面会解释);
  • (2)
    不管是kill还是pthread_kill, 信号只能被一个对应的线程所接收;
  • (3)
    SIGSTOP/SIGCONT信号只对一个线程起作用。

还好linuxthreads实现了第5点, 我认为这一点是最重要的. 如果某个线程”挂”了,
整个进程还在若无其事地运行着, 可能会出现很多的不一致状态.
进程将不是一个整体, 而线程也不能称为线程.
或许这也是为什么linuxthreads虽然与POSIX的要求差距甚远, 却能够存在,
并且还被使用了好几年的原因吧~。但是, linuxthreads为了实现这个”第5点”,
还是付出了很多代价, 并且创造了linuxthreads本身的一大性能瓶颈.

接下来要说说, 为什么A程序创建了10个线程,
但是ps时却会出现11个A进程了
. 因为linuxthreads自动创建了一个管理线程.
上面提到的”第5点”就是靠管理线程来实现的.当程序开始运行时,
并没有管理线程存在(因为尽管程序已经链接了pthread库,
但是未必会使用多线程). 程序第一次调用pthread_create时,
linuxthreads发现管理线程不存在, 于是创建这个管理线程.
这个管理线程是进程中的第一个线程(主线程)的儿子.然后在pthread_create中,
会通过pipe向管理线程发送一个命令, 告诉它创建线程. 即是说, 除主线程外,
所有的线程都是由管理线程来创建的, 管理线程是它们的父亲
.于是,
当任何一个子线程退出时,
管理线程将收到SIGUSER1信号(这是在通过clone创建子线程时指定的).
管理线程在对应的sig_handler中会判断子线程是否正常退出, 如果不是,
则杀死所有线程, 然后自杀.那么, 主线程怎么办呢? 主线程是管理线程的父亲,
其退出时并不会给管理线程发信号. 于是,
在管理线程的主循环中通过getppid检查父进程的ID号, 如果ID号是1,
说明父亲已经退出, 并把自己托管给了init进程(1号进程). 这时候,
管理线程也会杀掉所有子线程, 然后自杀. 那么,
如果主线程是调用pthread_exit主动退出的呢?
按照posix的标准,这种情况下其他子线程是应该继续运行的. 于是,
在linuxthreads中, 主线程调用pthread_exit以后并不会真正退出,
而是会在pthread_exit函数中阻塞等待所有子线程都退出了,
pthread_exit才会让主线程退出. (在这个等等过程中,
主线程一直处于睡眠状态.)

可见, 线程的创建与销毁都是通过管理线程来完成的,
于是管理线程就成了linuxthreads的一个性能瓶颈.
创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行,
并且多个请求会被管理线程串行地执行.

2.
NPTL

到了linux 2.6, glibc中有了一种新的pthread线程库–NPTL(Native POSIX
Threading Library). NPTL实现了前面提到的POSIX的全部5点要求. 但是,
实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.

在linux 2.6中, 内核有了线程组的概念,
task_struct结构中增加了一个tgid(thread group id)字段.
如果这个task是一个”主线程”, 则它的tgid等于pid,
否则tgid等于进程的pid(即主线程的pid).在clone系统调用中,
传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).

类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。

有了tgid,
内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程,
也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候,
线程就不要展现了).

而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,
而tast_struct中的pid则由gettid系统调用来返回.

在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002它们的tgid都是10001)。这时如果你kill
10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。

我们可以作如下验证,有程序段test.cpp如下:

#include< unistd.h> #include< stdlib.h> #include< stdio.h> #include< pthread.h> using namespace std;  void* doit(void*);  int main(void)  {  pthread_t tid;  pthread_create(&tid,NULL,doit,NULL);  pause();//主线程挂起否则主线程终止,子线程也就挂了)  }  void* doit(void* agr)  {  printf("thread is created!\n");  pause(); //挂起线程  } 

这个程序创建一个线程后挂起,子线程在输出“thread is
created!”也挂起。运行结果如图1.

图片 1

图1

之后查看进程pid,发现a.out的pid是23130,我们使用kill终止pid为23131的进程注意ps中并没有这个进程),如图2.

图片 2

图2

但是结果发现,a.out进程也终止了,如图3.其原因就是23131就是所创建线程的pid,线程异常终止了,进程也就终止了。

图片 3

图3

为了应付”发送给进程的信号”和”发送给线程的信号”,task_struct里面维护了两套signal_pending,
一套是线程组共享的,
一套是线程独有的.通过kill发送的信号被放在线程组共享的signal_pending中,
可以由任意一个线程来处理;
通过pthread_kill发送的信号(pthread_kill是pthread库的接口,
对应的系统调用中tkill)被放在线程独有的signal_pending中,
只能由本线程来处理.当线程停止/继续, 或者是收到一个致命信号时,
内核会将处理动作施加到整个线程组中.

3.
NGPT

说到这里, 也顺便提一下NGPT(Next Generation POSIX Threads).
上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体),
这种模型称为1:1模型(1个线程对应1个内核级线程);而NGPT则打算实现M:N模型(M个线程对应N个内核级线程),
也就是说若干个线程可能是在同一个执行实体上实现的.

线程库需要在一个内核提供的执行实体上抽象出若干个执行实体,
并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.大体上,
这可以通过为每个用户级线程分配一个栈,
然后通过longjmp的方式进行上下文切换. (百度一下”setjmp/longjmp”,
你就知道.)但是实际上要处理的细节问题非常之多.
目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.

用户级线程的切换显然要比内核级线程的切换快一些,
前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器,
进入然后退出内核态.
(进程切换则还需要切换地址空间等.)而用户级线程则不能享受多处理器,
因为多个用户级线程对应到一个内核级线程上,
一个内核级线程在同一时刻只能运行在一个处理器上.

不过, M:N的线程模型毕竟提供了这样一种手段,
可以让不需要并行执行的线程运行在一个内核级线程对应的若干个用户级线程上,
可以节省它们的切换开销.据说一些类UNIX系统(如Solaris)已经实现了比较成熟的M:N线程模型,
其性能比起linux的线程还是有着一定的优势.

在许多经典的操作系统教科书中 ,
总是把进程定义为程序的执行实例 , 它并不执行什么 ,
只是维护应用程序所需的各种资源…

1.进程与线程

   一个进程包含多个线程。可以把进程当做一个应用程序。

  
操作系统分配一段时间给一个进程,让CPU执行该进程。进程再将时间片段分割给各个线程

欢迎使用Markdown编辑器写博客

本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:

  • Markdown和扩展Markdown简洁的语法
  • 代码块高亮
  • 图片链接和图片上传
  • LaTex数学公式
  • UML序列图和流程图
  • 离线写博客
  • 导入导出Markdown文件
  • 丰富的快捷键

线程:轻量级进程,在资源、数据方面不需要进行复制

linux线程同步(5)-屏障,linux线程

一.概述                                                   

barrier(屏障)与互斥量,读写锁,自旋锁不同,它不是用来保护临界区的。相反,它跟条件变量一样,是用来协同多线程一起工作!!!

条件变量是多线程间传递状态的改变来达到协同工作的效果。屏障是多线程各自做自己的工作,如果某一线程完成了工作,就等待在屏障那里,直到其他线程的工作都完成了,再一起做别的事。举个通俗的例子:

1.对于条件变量。在接力赛跑里,1号队员开始跑的时候,2,3,4号队员都站着不动,直到1号队员跑完一圈,把接力棒给2号队员,2号队员收到接力棒后就可以跑了,跑完再给3号队员。这里这个接力棒就相当于条件变量,条件满足后就可以由下一个队员(线程)跑。

2.对于屏障。在百米赛跑里,比赛没开始之前,每个运动员都在赛场上自由活动,有的热身,有的喝水,有的跟教练谈论。比赛快开始时,准备完毕的运动员就预备在起跑线上,如果有个运动员还没准备完(除去特殊情况),他们就一直等,直到运动员都在起跑线上,裁判喊口号后再开始跑。这里的起跑线就是屏障,做完准备工作的运动员都等在起跑线,直到其他运动员也把准备工作做完!

二.函数接口                                           

1.创建屏障

1 #include <pthread.h>
2 
3 int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);

barrier:pthread_barrier_t结构体指针

attr:屏障属性结构体指针

count:屏障等待的线程数目,即要count个线程都到达屏障时,屏障才解除,线程就可以继续执行

2.等待

1 #include <pthread.h>
2 
3 int pthread_barrier_wait(pthread_barrier_t *barrier);

函数的成功返回值有2个,第一个成功返回的线程会返回PTHREAD_BARRIER_SERIAL_THREAD,其他线程都返回0。可以用第一个成功返回的线程来做一些善后处理工作。

3.销毁屏障

1 #include <pthread.h>
2 
3 int pthread_barrier_destroy(pthread_barrier_t *barrier);

三.简单例子                                           

写个简单的例子,主线程等待其他线程都完成工作后自己再向下执行,类似pthread_join()函数!

 1 /**
 2  * @file pthread_barrier.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <unistd.h>
 9 #include <pthread.h>
10 
11 /* 屏障总数 */
12 #define PTHREAD_BARRIER_SIZE 4
13 
14 /* 定义屏障 */
15 pthread_barrier_t barrier;
16 
17 void err_exit(const char *err_msg)
18 {
19     printf("error:%s\n", err_msg);
20     exit(1);
21 }
22 
23 void *thread_fun(void *arg)
24 {
25     int result;
26     char *thr_name = (char *)arg;
27 
28     /* something work */
29 
30     printf("线程%s工作完成...\n", thr_name);
31 
32     /* 等待屏障 */
33     result = pthread_barrier_wait(&barrier);
34     if (result == PTHREAD_BARRIER_SERIAL_THREAD)
35         printf("线程%s,wait后第一个返回\n", thr_name);
36     else if (result == 0)
37         printf("线程%s,wait后返回为0\n", thr_name);
38 
39     return NULL;
40 }
41 
42 int main(void)
43 {
44     pthread_t tid_1, tid_2, tid_3;
45 
46     /* 初始化屏障 */
47     pthread_barrier_init(&barrier, NULL, PTHREAD_BARRIER_SIZE);
48 
49     if (pthread_create(&tid_1, NULL, thread_fun, "1") != 0)
50         err_exit("create thread 1");
51 
52     if (pthread_create(&tid_2, NULL, thread_fun, "2") != 0)
53         err_exit("create thread 2");
54 
55     if (pthread_create(&tid_3, NULL, thread_fun, "3") != 0)
56         err_exit("create thread 3");
57 
58     /* 主线程等待工作完成 */
59     pthread_barrier_wait(&barrier);
60     printf("所有线程工作已完成...\n");
61 
62     sleep(1);
63     return 0;
64 }

28行是线程自己要做的工作,62行的sleep(1)让所有线程有足够的时间把自己的返回值打印出来。编译运行:

图片 4

可以看到,3个线程工作完成后才可以越过屏障打印返回值,第一个返回的是PTHREAD_BARRIER_SERIAL_THREAD,其他都是0。

这里有一点要注意:我们从运行结果看出,主线程打印”所有线程工作已完成”之后,线程1,线程2还在运行打印返回值。这个结果难免会误解”主线程等待所有线程完成工作之后再向下执行”。区分一点即可:等待只针对屏障之前的动作,越过屏障后,无论是主线程,还是子线程都会并发执行,如果非要让子线程完完全全执行完,可以再加个屏障到线程函数末尾,相应主线程也要加!

一.概述 barrier
(屏障)与互斥量,读写锁,自旋锁不同,它不是用来保护临界区的。相反,它跟条件变量一样…

2.创建线程(Thread)的方法

//方法一
Thread thread = new Thread(){
       @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();      //要写的代码
    } 
};//创建Thread类,重写run方法
thread.start()  //开启线程

 

//方法二
public class test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NewThread nt = new NewThread();
        Thread thread = new Thread(nt);//将该类放入Thread类中
        thread.start();//启动线程
    }
}

class NewThread implements Runnable{//创建一个继承Runnable接口的类

    @Override
    public void run() {
        // TODO Auto-generated method stub
        //要写的代码
    }

}

快捷键

  • 加粗 Ctrl + B
  • 斜体 Ctrl + I
  • 引用 Ctrl + Q
  • 插入链接 Ctrl + L
  • 插入代码 Ctrl + K
  • 插入图片 Ctrl + G
  • 提升标题 Ctrl + H
  • 有序列表 Ctrl + O
  • 无序列表 Ctrl + U
  • 横线 Ctrl + R
  • 撤销 Ctrl + Z
  • 重做 Ctrl + Y

           不间断地跟踪指令执行的路径被称为执行路线

注:所以说Thread类自身继承了Runnable();

Markdown及扩展

Markdown
是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。
—— [ 维基百科 ]

使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

本编辑器支持 Markdown Extra ,
 扩展了很多好用的功能。具体请参考Github.

 

3.线程的生命周期

图解

图片 5

表格

Markdown Extra 表格语法:

项目 价格
Computer $1600
Phone $12
Pipe $1

可以使用冒号来定义对齐方式:

项目 价格 数量
Computer 1600 元 5
Phone 12 元 12
Pipe 1 元 234

 进程的结构:task_struck;地址空间

理解: 线程具有7种状态

        
出生:在Thread运用start()方法之前都算出生状态                                        
就绪:当start()方法完成之后线程处于就绪状态(等待执行)

        
运行:操作系统分配时间,让线程进入cpu运行                                             

         产生阻塞的三种方法(也是三种状态):

         等待:利用thread1.wait()线程自动进入阻塞状态。

                
一种设置时常:thread1.wait(1000);//当过了1000毫秒后自动处于就绪状态

                
另一种不设市场:只能够其他线程利用thread1.notify()唤醒,否则将一直处于阻塞状态。

         睡眠:利用thread.sleep()进入睡眠状态。

                 必须设置时长,当时间过了自动进入就绪状态

         阻塞:当线程处于等待I/O的输入输出方可再次运行时,处于阻塞状态。等到输入输出完毕时候自动进入就绪状态。

         最后

         线程运行完之后处于死亡状态

定义列表

Markdown Extra 定义列表语法:
项目1
项目2
定义 A

定义 B

项目3
定义 C

定义 D

定义D内容

 线程:轻量级的进程

4.线程的优先级

    优先级高的线程先运行,低的后运行。

thread.setPriority(int rank);//设置优先级

thread.yield()//让比自己高优先级的线程先运行

代码块

代码块语法遵循标准markdown代码,例如:

@requires_authorization
def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
        print 'Greater'
    return (param2 - param1 + 1) or None
class SomeClass:
    pass
>>> message = '''interpreter
... prompt'''

       在同一个进程中创建的线程,在共享进程的地址空间

5.其他主要方法

//join()方法 Thread thread1 = new Thread(){
   public void run (){
        thread2.join();  //thread2调用了join方法
   }  
};

Thread thread2 = new Thread(){
    public void run (){
        //
   }  
}
//所以thread1要等待thread2执行完毕才能执行

脚注

生成一个脚注1.

   在linux里用task_struct来描述一个线程,进程和线程都参与统一的调度

6.线程同步  

目录

[TOC]来生成目录:

  • 欢迎使用Markdown编辑器写博客
    • 快捷键
    • Markdown及扩展
      • 表格
      • 定义列表
      • 代码块
      • 脚注
      • 目录
      • 数学公式
      • UML
    • 离线写博客
    • 浏览器兼容

 线程是共享相同地址空间的多个任务

  1.什么叫线程同步,为什么需要线程同步

    
当多个线程同时修改或调用同一个资源的时候,可能会导致数据不一致的情况,为了防止这种情况。就需要当一个线程使用资源的时候,另一个线程需要等待前一个线程使用完成。

数学公式

使用MathJax渲染LaTex
数学公式,详见math.stackexchange.com.

  • 行内公式,数学公式为:Γ(n)=(n−1)!∀n∈N
  • 块级公式:

x=−b±b2−4ac−−−−−−−√2a

更多LaTex语法请参考
这儿.

   ————————————————————————————

  2.线程锁

     将资源对象锁住,防止其他线程使用。

//方法一
Thread thread = new Thread(){
       @Override
    public void run() {
        // TODO Auto-generated method stub
       super.run();
    synchronized(){
           //锁住资源对象
      }
    }
} 
//方法二
Thread thread = new Thread(){
       @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        use();
    }

    private synchronized  void use(){
       //锁住资源对象
    }  
} 
//所以说synchronized是一个修饰符

     

UML 图:

可以渲染序列图:

Created with Raphaël 2.1.0张三张三李四李四嘿,小四儿,
写博客了没?李四愣了一下,说:忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.1.0开始我的操作确认?结束yesno

  • 关于 序列图 语法,参考
    这儿,
  • 关于 流程图 语法,参考
    这儿.

     一个进程中的多个线程共享以下资源:

 7.小技巧

    1.如何控制线程死亡:

boolean isDead = true;

Thread thread = new Thread(){
       @Override
    public void run() {
        // TODO Auto-generated method stub
       super.run();
       while(isDead){
          if (...){
             //... 
          }
          else{
            isDead = false;
          }
       }    
    }
}        

 

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。 

用户可以选择
把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

    1.代码段/指令

浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入


  1. 这里是 脚注内容.

** 2.静态数据(全局变量、静态变量)

3.进程中打开的文件描述符

4.信号处理函数

5.当前工作目录

6.用户id:uid

7.组id:gid

  

 每个线程私有的资源如下:

    1.线程id:tid

2.程序计数器(PC)和寄存器

** 3.栈/堆栈(stack):局部变量

4.错误码(errno)

5.信号掩码

6.执行状态和属性

 ———————————————————————————————–

    创建线程:

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

            thread:创建的线程的id号

attr:指定线程的属性,NULL—–表示使用缺省属性,默认

start_routine:线程执行的函数         

  void *(*start_routine) (void
*)————–参数和返回值都被定义为类型是void*的指针,以允许他们指向任何类型的值

arg:传递给线程执行的函数的参数

 

删除线程/线程退出:

         void pthread_exit(void *retval);

     retval:线程退出时,返回值的地址

 

控制线程:等待一个线程结束/以阻塞的方式等待线程的结束

            int pthread_join(pthread_t thread, void **retval);

   thread:要等待的进程

   retval:指向线程返回值的地址,不需要的话—NULL

======================================================================================================

|
   在进行多线程编程时,一般主线程初始化/创建其他线程后,不做任何操作,调用pthread_join等待线程结束
|

| 因为若主线程有操作的话,可能因为操作失误而关闭进程,这样就会影响其他的线程操作
                  |

======================================================================================================

 

线程间互斥和同步:

  —————————————————————————————————————————————————-

  线程间同步(条件变量):

     条件变量是线程的一种同步机制。

 条件变量给多个线程提供一个汇合的场所

 条件变量是公共资源,条件变量与互斥锁一起使用,允许线程以没有竞争的方式等待特定的条件发生。

 条件变量是由互斥锁进行保护的。线程在改变条件变量的状态之前,必须先锁住互斥锁。

          

    初始化条件变量:   

  int pthread_cond_init(pthread_cond_t *restrict cond,const
pthread_condattr_t *restrict attr);

     attr—->条件变量的属性

 NULL—->默认属性

  或者

  pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    等待条件变量:

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

     功能:1.先解锁

       2.暂停该线程

唤醒条件变量:

      int pthread_cond_signal(pthread_cond_t *cond);

     功能:使一个由cond阻塞的线程运行

  ———————————————————————————————————————————————————–

  线程间互斥:引入互斥锁(mutual
exclusion)目的是用来保证共享数据操作的完整性和正确性

 

    互斥锁:主要用来保护临界资源。每个临界资源都由一个互斥锁来保护,任何时候最多只有一个线程能访问临界资源。进程必须要先获得互斥锁才能访问临界资源,访问完临界资源后释放互斥锁。如果无法获得互斥锁,线程就会阻塞/等待,直到获得锁为止。

 

           初始化互斥锁:int
pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr);

                    mutex———-互斥锁

attr———-互斥锁的属性,NULL—表示缺省/默认属性

 或者

 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

   加锁:等待互斥锁解开然后再锁住互斥锁

        int pthread_mutex_lock(pthread_mutex_t *mutex);

   解锁:int
pthread_mutex_unlock(pthread_mutex_t *mutex);

  —————————————————————————————————————————————————————–

    信号量:bijkstra算法

   信号量也是一种同步的机制

   由信号量来决定线程是继续运行还是阻塞等待

 

   信号量代表一类资源,他的值表示系统中该资源的数量

   信号量是受保护的变量,只能通过函数来访问

 

   初始化信号量:

        int sem_init(sem_t *sem, int pshared, unsigned int value);

   sem:初始化的信号量

   pshared:信号量的共享范围,0—–线程之间   !0——进程之间

   value:信号量的初始值

 

      申请资源( P 操作):

 if(信号量 > 0)

 {

  信号量-1;

  申请资源的任务继续运行;

 }else

 {

申请资源的任务阻塞等待;

 }

 

   int sem_wait(sem_t *sem);

   int sem_trywait(sem_t *sem);

   int sem_timedwait(sem_t *sem, const struct timespec
*abs_timeout);

 

  释放资源( V 操作):

     if(没有任务等待资源)

 {

  信号量+1;

 }else

 {

  唤醒第一个等待的任务,让这个任务继续运行;

 }

 

  int sem_post(sem_t *sem);   //唤醒信号量

  ———————————————————————————————————————————————

   Posix 定义的信号量:

   **   无名信号量

有名信号量

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图