Linux Posix Thread

在一个程序里的一个执行路线(routine)就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。一切进程至少都有一个执行线程。
进程与线程
  进程是资源竞争的基本单位
  线程是程序执行的最小单位
线程共享进程数据,但也拥有自己的一部分数据
  线程ID
  程序计数器 PC指针
  寄存器组
  数据栈
  errno
一个进程内部的线程可以共享资源
  代码段
  数据段
  打开文件和信号
程序 放在 磁盘 静态数据,一堆指令的集合,数据+指令组成

进程 程序的动态执行过程,相当于CPU的调度,数据段,堆栈段,代码段 PCB控制块

linux通过PCB来控制进程

线程 linux 执行的最小 单元,线程体就是一个函数调用。

fork和创建新线程的区别
当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID。这个新进程的运行时间是独立的,它在执行时几乎完全独立于创建它的进程。在进程里面创建一个新线程的时候,新的执行线程会拥有自己的堆栈(因此也就有自己的局部变量),但要与它的创建者共享全局变量、文件描述符、信号处理器和当前的工作目录状态
线程的优点
创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程缺点
性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高
编写与调试一个多线程程序比单线程程序困难得多

线程调度竞争范围
操作系统提供了各种模型,用来调度应用程序创建的线程。这些模型之间的主要不同是:在竞争系统资源(特别是CPU时间)时,线程调度竞争范围(thread-scheduling contention scope)不一样。
进程竞争范围(process contention scope):各个线程在同一进程竞争“被调度的CPU时间”(但不直接和其他进程中的线程竞争)。
系统竞争范围(system contention scope):线程直接和“系统范围”内的其他线程竞争。
线程的属性是可以修改的,默认情况下是系统竞争范围。

线程调用API熟悉

pthread_create函数创建一个新的线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数,线程的执行体
arg:传给线程启动函数的参数
成功返回0;失败返回错误码 

线程执行完函数调用就消失了,不会在执行pthread_create下面的代码,这个是和进程的区别。一般请款下,进程和线程是并行运行的。线程是依赖于进程之上,如果进程死了,线程也要死。进程不一样,父进程死了,子进程也可以自己运行,因为子进程有自己的独立的内存空间。线程依赖于进程的生命周期,这一点和子进程是不同的,

 pthread_self函数返回线程ID
pthread_t pthread_self(void);
返回值:总是成功返回调用者的TID

pthread_join函数等待子线程结束
int pthread_join(pthread_t thread, void **value_ptr);
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值 会送给父进程的运算结果
成功返回0;失败返回错误码

pthread_exit函数线程终止
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

pthread_cancel函数取消一个执行中的线程
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
成功返回0;失败返回错误码

在进程中,如果父进程不管子进程,需要忽略SIGCHLD信号

pthread_detach函数将一个线程分离
int pthread_detach(pthread_t thread);
thread:线程ID
返回值:成功返回0;失败返回错误码

脱离进程运行的函数,

一般情况下,调用线程执行体,马上调用detach函数调用。在父进程中同样调用pthrea_join,这样就可以避免了父进程的阻塞状态。

错误检查
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回。
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

线程和进程之间传递数据

通过全局变量来传递数据

通过参数来传递数据,通过参数来传递的数据不能是线程栈的局部变量,因为线程栈的局部变量回被销毁。

如果使用pthread_detach函数,用pthread_exit是不能将数据给甩出去数据。但是使用return就可以。

使用欧冠pthread_exit()的效果和return的效果是一样的。

自己测试detach函数和join函数一起使用的时候不稳定

一般情况下很少在线程中将运算结果给甩出来,线程的运行结果告诉父进程,只需要一个int变量就可以了。

如果父进程死了子线程也会死,这个时候就需要联合考虑join和detach函数了,以及子线程还有父线程结束先后的问题了。

线程的属性 设置 (一般使用默认属性就可以了)

线程分离属性

线程的栈属性 10M

栈溢出保护区、

线程竞争范围

线程的调度策略 随机的

设置调度优先级 默认是0

并发性 按照自己最适合的方式去映射

进程互斥用信号量,线程也可以用信号量来互斥。信号量很大

线程也有锁,线程锁,用的最多的就是POXIS互斥锁

分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create()函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用。

多线程同步问题:

(1)线程共享进程的资源和地址空间

(2)任何线程对系统资源的操作都会给其他线程带来影响

多线程同步方法:

互斥锁 线程级的方式

信号量  进程级别的方式

条件变量

posix互斥锁

互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。

互斥锁机制主要包括下面的基本函数。

互斥锁初始化:pthread_mutex_init()

互斥锁上锁:pthread_mutex_lock()

互斥锁判断上锁:pthread_mutex_trylock()

互斥锁接锁:pthread_mutex_unlock()

消除互斥锁:pthread_mutex_destroy()

其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。默认属性为快速互斥锁。可以通过锁的属性来进行设置。

通过锁机制 ,可以创建一个临界区,使得临界区的代码成为一个原子操作。

只在进程中做竞争的线程称为用户线程,在这个系统做竞争的线程映射称为轻量级进程,对于这么多线程linux是通过linux线程来进行控制和管理的。

在起线程的时候

  不互斥(不枷锁)  不分离   进程等待 OK

     互斥(加锁)      不分离   进程等待 OK

  不互斥(不枷锁)   分离    进程等待 NOK

  不互斥(不枷锁)   分离    进程不等待 NOK

在线程开发中,避免多个线程同时修改一个变量的值


int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
pthread_mutexattr_init()函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为未锁住态。
互斥锁属性
使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。
要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改
销毁互斥锁对象
pthread_mutexattr_destroy()可用来取消分配用于维护 pthread_mutexattr_init() 所创建的属性对象的存储空间。
pthread_mutexattr_destroy 语法
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
pthread_mutexattr_destroy() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。EINVAL 描述: 由 mattr 指定的值无效。

线程的同步和互斥,以及一个简单的PC模型

#include<string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

//定义锁并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//定义条件并初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

#define CUSTOM_COUNT 2
#define PRODUCT_COUNT 4

int g_Count = 0;

void *consume(void* arg)
{
    int inum = (int)arg;
    
    while(1)
    {
        pthread_mutex_lock(&mutex);
        
            printf("consum  %d\n", inum);
            while(g_Count == 0)
            {
                printf("consum:%d 开始等待\n", inum);
                pthread_cond_wait(&cond, &mutex); 
                printf("consum:%d 醒来\n", inum);
            }
            printf("consum:%d 消费产品begin\n", inum);
            g_Count--; //消费产品
            printf("consum:%d 消费产品end\n", inum);
        
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    
    pthread_exit(0);

} 

//生产者线程
void *produce(void* arg)
{
    int inum = (int)arg;
    
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(g_Count>17)
        {
            pthread_mutex_unlock(&mutex);
            sleep(1);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
        }
        
        pthread_mutex_lock(&mutex);
        
            printf("产品数量: %d\n", g_Count);
            printf("produce:%d 生产产品begin\n", inum);
            g_Count++;
            //只要我生产出一个产品,就告诉消费者去消费
            printf("produce:%d 生产产品end\n", inum);
            
            printf("produce:%d 发条件signal begin\n", inum);
            pthread_cond_signal(&cond); //通知,在条件上等待的线程 
            printf("produce:%d 发条件signal end\n", inum);
            
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    
    printf("produce %d\n", inum);
    pthread_exit(0);

} 

int main()
{
    pthread_t    tidArray[CUSTOM_COUNT+PRODUCT_COUNT];
    
    //创建消费者线程
    for(int i=0; i<CUSTOM_COUNT; i++)
    {
        pthread_create(&(tidArray[i]), NULL, consume, (void *)i);
    }
    
    //创建生产线程
    for (int i=0; i<PRODUCT_COUNT; i++)
    {
        pthread_create(&(tidArray[i+CUSTOM_COUNT]), NULL, produce,(void *)i);
    }
    
    for (int i=0; i<CUSTOM_COUNT+PRODUCT_COUNT; i++)
    {
        pthread_join(tidArray[i], NULL); //等待线程结束
    }
    
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/randyniu/p/9189112.html