系统编程——线程

线程简要概述:

1.编译连接的时候使用 -pthread

2.LWP:是线程号,标识线程身份,交给CPU用的,划分时间轮片
    线程ID:是进程对自己内部线程加以区别的标识。两个进程中,线程号允许相同

 typedef unsigned long int pthread_t;

1.pthread_create()


1.int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create()用于创建线程
thread: 接收创建的线程的 ID
attr: 指定线程的属性       不设置写NULL是默认,不是说没有 
start_routine: 指定线程函数
arg: 给线程函数传递的参数 (不设置写NULL)  就是参数3函数的参数 
成功返回 0, 失败返回错误码

2.pthread_exit()

将单个线程退出
void pthread_exit(void *retval); 参数:retval 表示线程退出状态,通常传 NULL

3.pthread_self ()

获取线程 ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void);返回值:成功:0; 失败:无!
线程 ID:pthread_t 类型,本质:在 Linux 下为无符号整数(%lu),其他系统中可能是结构体实现
线程 ID 是进程内部,识别标志。(两个进程间,线程 ID 允许相同)
注意:不应使用全局变量 pthread_t tid,在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用pthread_self。

4.pthread_join()

阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
参数:thread:线程 ID (【注意】:不是指针);retval:存储线程结束状态。
对比记忆:
进程中:main 返回值、exit 参数-->int;等待子进程结束 wait 函数参数-->int *
线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **

5.pthread_detach() 

实现线程分离
int pthread_detach(pthread_t thread); 成功:0;失败:错误号

    线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而
直接自己自动释放。网络、多线程服务器常用。

    进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资
源仍存于系统中,导致内核认为该进程仍存在。

    也可使用 pthread_create 函数参 2(线程属性)来设置线程分离。

6.pthread_cancle()

杀死(取消)线程 其作用,对应进程中 kill() 函数。
int pthread_cancel(pthread_t thread); 成功:0;失败:错误号

    注意:cancel 它有时候可以杀死线程,有些情况却不能。
cancel 终止线程 必须要到达一个取消点才行,不像之前进程里面的信号,信号的优先级比程序执行的优先级高
必须要先去处理信号。现在cancle 杀死线程,必须要有一个契机进内核,也就是必须要到达一个取消点。
不管是printf、还是sleep ,都有底层系统调用,一调用就进内核了,也就是说到达了自己的取消点。
有了这个取消点就可以把这个线程杀死。

    取消点(保存点):是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat,open,pause,close,read,write..... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
(printf、sleep )

可粗略认为一个系统调用(进入内核)即为一个取消点 。如线程中没有取消点,可以通过调用 pthread_testcancel()函数自行设置一个取消点。

被取消的线程, 退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。因此当我们对一个已经被取消的线程使用 pthread_join
回收时,得到的返回值为-1

控制原语对比

进程 线程
fork pthread_create
exit pthread_exit
wait pthread_join
kill pthread_cancel
getpid pthread_self 命名空间

演示:循环创建多个子进程

注意:   

 1.在创建线程时,若连续多次创建线程,并且要将特定参数传给线程时,可能会出现地址上的旧值还未成功赋予,地址上的旧值就被新值代替
     #怎么解决?
     #在创建线程后等待一段时间,确保值被成功赋予

 2.线程之间共享全局变量

 3.线程中,禁止使用 exit 函数,会导致进程内所有线程全部退出。使用 pthread_exit 函数 体面退出
        #exit 是退出进程,直接就把进程给结束了
        #return 是返回到函数调用者那里去  (如果在主线程用则相当于终止进程)

   4.各个线程退出(包括主线程),不影响其他线程

 pthread.cpp

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>                                                                                                                                      
#define SIZE 5
int val=0;
void*Pthread_fun(void*arg)
{
    int i=*((int*)arg)+1;   
    if(i==2) { printf("2号线程退出辣 !\n"); pthread_exit(NULL);}    // 1. use: exit(0) ?  2. use : return ?
    printf("I'am pthread :pthreadid is %u  and pid is %d  :number is %d\n",pthread_self(),getpid(),i);
    switch(i){                                                  // test allround variable
        case 1: { val=100;printf("val is %d\n",val);} break;
        case 2: { val=200;printf("val is %d\n",val);} break;
        case 3: { val=300;printf("val is %d\n",val);} break;
        case 4: { val=400;printf("val is %d\n",val);} break;
    }  
}
int main(int argc,char*argv[])
{
    printf("本程序用于演示循环创建多个子线程 \n\n");
    printf("init val :%d \n",val); 

    int i=0;
    char*reval=NULL;
    pthread_t pthreadid;
   
    for(;i<SIZE;i++)                //create 5 pthread
    {  
        if( pthread_create(&pthreadid,NULL,Pthread_fun,&i) != 0 ) {printf("thread error\n"); return 0; }   
        sleep(1);                //this where must sleep
    }  
        printf("I am main_pthread  pid is %d  pthreadid is %u \n",getpid(),pthreadid);
        printf("laster val :%d \n",val);
   
    return 0; 
}

疑问:(指针变量不能直接用?? 必须要有空间,用malloc申请,才可以存数据?)

演示:终止线程的方式 和 retval 非空用法

终止线程方式:
    总结:终止某个线程而不终止整个进程,有三种方法:
    1. 从线程主函数 return。这种方法对主控线程不适用,从 main 函数 return 相当于调用 exit。
    2. 一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。
    3. 线程可以调用 pthread_exit 终止自己

retval 非空用法:

        调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止。thread 线程以不同的方法终止,通过 pthread_join

得到的终止状态是不同的,总结如下:

        1. 如果 thread 线程通过 return 返回,retval 所指向的单元里存放的是 thread 线程函数的返回值。
        2. 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉,retval 所指向的单元里存放的是常数PTHREAD_CANCELED。
        3. 如果 thread 线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。
        4. 如果对 thread 线程的终止状态不感兴趣,可以传 NULL 给 retval 参数

 pthread_eixt_join.cpp

#include<stdio.h>
#include<pthread.h>                                                                                                                                                                                                                                   
#include<stdlib.h>
#include <unistd.h>
void* Pthread_fun_1(void*arg)                 // return 退出
{
    printf("Pthread_fun_1 :我正在奔跑\n");
    return (void*)111;
}
void* Pthread_fun_2(void*arg)                   //pthread_exit 退出
{
    printf("Pthread_fun_2 :我正在奔跑\n");
    pthread_exit((void*)222);
}
void* Pthread_fun_3(void*arg)                 //被 pthread_cancel 杀死
{
    while(1)
    {
        printf("Pthread_fun_3 :我正在奔跑\n");
        sleep(1);
        //void pthread_testcancel(void);自己加取消点
    }
    return (void*)666;
}
int main()
{
    printf("\n本程序演示用不同的退出方式测试join参数retval的内容 \n");
    printf("未判断各个函数的返回值 \n\n");

    pthread_t pthreadid=-1;
    void*tret=0;

    //这里的类型转换 没有错 ? 8字节的指针 转成 4字节的 int型 ?
    pthread_create(&pthreadid,NULL,Pthread_fun_1,NULL);
    pthread_join(pthreadid,&tret);
    printf("Pthread_fun_1 exit tret : %d \n",(int)tret);    //tret: 111

    pthread_create(&pthreadid,NULL,Pthread_fun_2,NULL);
    pthread_join(pthreadid,&tret);
    printf("Pthread_fun_2 exit tret : %d \n",(int)tret);    // tret: 222

    pthread_create(&pthreadid,NULL,Pthread_fun_3,NULL);  sleep(3); // 3 秒后杀死它
    pthread_cancel(pthreadid);
    pthread_join(pthreadid,&tret);        //被cancle 杀死的进程任然可以回收 
    printf("Pthread_fun_3 exit tret : %d \n",(int)tret);   // tret:默认值: PTHREAD_CANCELED。(-1)

    return 0;

}

 疑问:类型转换有问题

演示:线程分离 和  处理线程的返回值

        使用strerror(int errnum):它会直接errnum 进行翻译,返回一个字符串给你,让你知道具体的含义 

       在处理返回值时,之前一直都是用 perror(),实际上它在底层会访问errornumber,把errornumber全局变量进行翻译,翻译成字符串,在和str拼接。
      但是在线程里面这些没用了。
      线程是怎么做的?
   线程直接把错误号返回回来了

#include <stdio.h>                                                                                                                                        
#include <unistd.h>            
#include <stdlib.h>            
#include <pthread.h>           
#include <string.h>            
void * Pthread_fun(void*arg)   
{ 
    printf("I,am runing \n");  
    printf("pthread:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
    return NULL;               
} 
int main()
{ 
    printf("\n本程序用于测试pthread_detach 和 处理返回值\n\n");
    pthread_t pthreadid=-1;    
    int ret=-1;
  
    ret=pthread_create(&pthreadid,NULL,Pthread_fun,NULL);
    if(ret!=0) { fprintf(stderr,"pthread_create error: %s\n",strerror(ret)); exit(0); }  //这样处理会知道数字代表什么含义
    pthread_detach(pthreadid);                      // 设置线程分离,线程终止,会自动清理pcb 

    sleep(1);
    ret=pthread_join(pthreadid,NULL);     //pthreadid 被回收了 ,所以显示 无效的参数
    printf("join ret=%d \n",ret);                   // cat ret :22                  
    if(ret!=0) { fprintf(stderr,"pthread_detach error: %s\n",strerror(ret)); exit(0); }  // Invalid argument 

    printf("main:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
    pthread_exit(NULL);
}

线程属性

        之前我们讨论的线程都是采用 线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需 要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。

           

 最主要的作用:设置线程的分离状态

        虽然detach也可以设置分离,但是每次创建线程,多了,回收比较麻烦,一开始就把它的分离属性给修改。

线程属性的初始化

注意:应先初始化线程属性,再 pthread_create 创建线程
初始化线程属性

int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号
销毁线程属性所占用的资源

int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号

线程属性的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。

非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当 pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。

分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该
根据自己的需要,选择适当的分离状态。

线程分离状态的函数:

设置线程属性,分离 or 非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

获取程属性,分离 or 非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); 

参数: attr:已初始化的线程属性
detachstate: PTHREAD_CREATE_DETACHED(分离线程)
              PTHREAD _CREATE_JOINABLE(非分离线程)

演示:设置线程属性,来设置线程分离

pthread_set_attr.cpp

#include <stdio.h>                                                                                                                                        
#include <unistd.h>            
#include <stdlib.h>            
#include <pthread.h>           
#include <string.h>            
void * Pthread_fun(void*arg)   
{ 
    printf("I,am runing \n");  
    printf("pthread:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
    return NULL;               
} 
int main()
{ 
    printf("\n本程序用于演示设置线程属性,来设置线程分离\n\n");
    pthread_t pthreadid=-1;    
    int ret=-1;
    pthread_attr_t attr;       
  
    ret=pthread_attr_init(&attr);               //初始化attr 
    if(ret != 0) { fprintf(stderr,"pthread_attr_init error :%s \n",strerror(ret)); exit(0); }
   
    ret=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);    //设置线程属性为分离状态
    if(ret!=0) { fprintf(stderr,"pthread_attr_setdetachstate error: %s\n",strerror(ret)); exit(0); }

    ret=pthread_create(&pthreadid,&attr,Pthread_fun,NULL);   //创建线程
    if(ret!=0) { fprintf(stderr,"pthread_create error: %s\n",strerror(ret)); exit(0); }

    ret=pthread_attr_destroy(&attr);              //销毁 attr 
    if(ret != 0) { fprintf(stderr,"pthread_attr_destroy error :%s \n",strerror(ret)); exit(0); }

    ret=pthread_join(pthreadid,NULL);           //通过join函数 也可以判断线程是否是分离属性 如果失败就是分离的;
    if(ret != 0) { fprintf(stderr,"pthread_attr_join error :%s \n",strerror(ret)); exit(0); }
    
    printf("main:pid=%d ,pthreadid=%u \n",getpid(),pthread_self());
    pthread_exit(NULL);

}

线程使用注意事项

1. 主线程退出其他线程不退出,主线程应调用 pthread_exit

2. 避免僵尸线程
   pthread_join
   pthread_detach
   pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

3. malloc 和 mmap 申请的内存可以被其他线程释放、
(malloc 和 mmap 申请出来就是地址,线程之间地址是共享的,所以彼此之间可以释放)

4. 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程
中均 pthread_exit

5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

(如果有多个线程,来了一个信号,默认情况下是谁抢到谁处理,如果想指定其中的哪一个处理
,每个线程有各自独立的屏蔽字,但是它们共享信号的处理方式,未决信号集是共享的) ??

线程安全--线程同步

同步:同步即协同步调,按预定的先后次序运行。

出现线程安全时:

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

 一、互斥锁 mutex:

 Linux 中提供一把互斥锁 mutex(也称之为互斥量)。

1.pthread_mutex_init 函数

初始化一个互斥锁(互斥量) ---> 初值可看作 1

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

参 1:传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过除本指针以外的其他变量或指针修改

参 2:互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)。 参 APUE.12.4 同步属性
1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接
使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)

2.pthread_mutex_destroy 函数

销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

3.pthread_mutex_lock 函数

加锁。可理解为将 mutex--(或 -1),操作后 mutex 的值为 0。
int pthread_mutex_lock(pthread_mutex_t *mutex);

4.pthread_mutex_unlock 函数

解锁。可理解为将 mutex ++(或 +1),操作后 mutex 的值为 1。
int pthread_mutex_unlock(pthread_mutex_t *mutex);

5.pthread_mutex_trylock 函数

尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

lock 加锁失败会阻塞,等待锁释放。
trylock 加锁失败直接返回错误号(如:EBUSY),不阻塞。

演示:给线程加互斥锁

在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。

(锁持有的时间越短越好)

 pthread_mutex.cpp

#include <stdlib.h>            
#include <assert.h>            

pthread_mutex_t mutex;              // 创建互斥锁
void *tfn(void *arg)           
{ 
    srand(time(NULL));         
    while (1) {                
        if(pthread_mutex_lock(&mutex) != 0) { printf("pthread_mutex_lock error \n"); return 0; }      // P                                                                      
        printf("hello ");      
        sleep(rand() % 3); /*模拟长时间操作共享资源,导致 cpu 易主,产生与时间有关的错误*/
        printf("world\n");     
        sleep(rand() % 3);     
        if(pthread_mutex_unlock(&mutex) !=0 ){ printf("pthread_mutex_unlock error \n"); return 0; }   //V
        sleep(rand() % 3);    // 加上给CPU进程切换的时间,不会使其他线程饥饿
    }
    return NULL;               
} 
int main(void)
{
    printf("\n本程序用于演示加互斥锁来控制 大小写一次性输出\n\n");

    pthread_t tid;
    pthread_mutex_init(&mutex,NULL);                    //init mutex  :为1             

    srand(time(NULL));
    int ret= pthread_create(&tid, NULL, tfn, NULL);   assert(ret==0);
    while (1) {
        if(pthread_mutex_lock(&mutex) != 0) { printf("pthread_mutex_lock error \n"); return 0; }   // P
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
        if(pthread_mutex_unlock(&mutex) !=0 ){ printf("pthread_mutex_unlock error \n"); return 0; } // V
        sleep(rand() % 3);
    }
    pthread_join(tid, NULL);
    pthread_mutex_destroy(&mutex);       //销毁互斥量

    return 0;
}

从上面程序演示产生死锁的情况:

        1. 线程试图对同一个互斥量 A 加锁两次

        2. 线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁

 二、读写锁

介绍: 

            读写锁也叫共享-独占锁
            锁只有一把 !!
            以读的方式给数据加锁:读锁
            以写的方式给数据加锁:写锁

             用的不多

特性:

                读共享,写独占
                如果读锁和写锁一起争,写锁优先级高

读写锁非常适合于对数据结构读的次数远大于写的情况。

1.pthread_rwlock_init 函数

初始化一把读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参 2:attr 表读写锁属性,通常使用默认属性,传 NULL 即可。

2.pthread_rwlock_destroy 函数

销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

3.pthread_rwlock_rdlock 函数

以读方式请求读写锁。(常简称为:请求读锁)
 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

4.pthread_rwlock_wrlock 函数

以写方式请求读写锁。(常简称为:请求写锁)
 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

5.pthread_rwlock_unlock 函数

解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

6.pthread_rwlock_trywrlock 函数

非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

7.pthread_rwlock_tryrdlock 函数

非阻塞以读方式请求读写锁(非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

演示:多个线程对同一全局数据读、写操作

#include <stdio.h>                                                                                                                                                                                                                                    
#include <unistd.h>            
#include <pthread.h>           
int counter=0;            //全局变量,用于观察变化        
pthread_rwlock_t rwlock;       
/* 3 个线程不定时写同一全局资源,5 个线程不定时读同一全局资源 */
void *th_write(void *arg)      
{ 
    int t=0;
    int i=*((int*)arg);        
    while (1) {                
        pthread_rwlock_wrlock(&rwlock);         //以写的方式请求加读写锁        
        t = counter;        //把写之前的值记录,进行对比    
        usleep(1000);                   //这是独占,不会失去CPU         
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);         //解读写锁
        usleep(10000);         
    }
    return NULL;
}
void *th_read(void *arg)
{
    int i=*((int*)arg);
    while (1) {
        pthread_rwlock_rdlock(&rwlock);         //以读的方式请求加读写锁        
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);         //解读写锁
        usleep(2000);
    }
    return NULL;
}
int main(void)
{
    printf("\n本程序用于演示读写锁,函数未判断返回值 \n\n");

    int i;
    pthread_t tid[8];
    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write,&i); 
    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read,&i); 
    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);     

    pthread_rwlock_destroy(&rwlock);        //销毁读写锁

    return 0;

}

从中可以分析出读写锁的特性

 三、条件变量

介绍:条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

优点相较于 mutex 而言,条件变量可以减少竞争。 (不太理解)

        如直接使用 mutex ,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚 (链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引
起消费者之间的竞争。提高了程序效率。

1.pthread_cond_init 函数

初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参 2:attr 表条件变量属性,通常为默认值,传 NULL 即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2.pthread_cond_destroy 函数

销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

3.pthread_cond_wait 函数

阻塞等待一个条件变量
 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
1. 阻塞等待条件变量 cond(参 1)满足
2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
3. 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);

注意:也就是说在这之前隐含了
第三个参数是mutex ,结合互斥锁一起使用。
使用这个函数:
1.得要先定义互斥锁
2.而且初始化互斥锁
3.而且还隐含了已经完成了加锁操作!!!

4.pthread_cond_timedwait 函数

限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec 
*restrict abstime);
参 3: 参看 man sem_timedwait 函数,查看 struct timespec 结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参 abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参 APUE.11.6 线程同步条件变量小节
在讲解 setitimer 函数时我们还提到另外一种时间类型:
 struct timeval {
 time_t tv_sec; /* seconds */ 秒
 suseconds_t tv_usec; /* microseconds */ 微秒
 };

5.pthread_cond_signal 函数

唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);

阻塞等待的线程怎么样知道等待阻塞的条件满足了??、
就是靠这个函数来通知

6.pthread_cond_broadcast 函数

唤醒全部阻塞在条件变量上的线程
 int pthread_cond_broadcast(pthread_cond_t *cond);

演示用条件变量实现生产者消费者模型

生产者消费者模型
消费者:                                                               生产者
1.创建锁、条件变量                                        1.生产数据
2.初始化锁                                                       2.加锁
3.加锁                                                               3.访问公共区域 (生产数据)
4等待条件满足                                                 4.解锁
        1)阻塞等条件变量                                      5.通知阻塞在条件变量上的线程
        2)解锁                                                       6.循环生产后续数据
        3)加锁
5.访问共享数据
6.解锁、释放条件变量。释放锁

//借助条件变量模拟 生产者.消费者问题
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
//链表作为共享数据,需被互斥量保护                                                                                                                                           
struct msg {
    struct msg *next;
    int num;
};

struct msg *head;

//静态初始化 一个条件变量 和 一个互斥量
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p)
{
    struct msg *mp;
    for (;;) {
        pthread_mutex_lock(&lock);     //加互斥锁
        while (head == NULL) {   //头指针为空,说明没有节点 可以为 if 吗
            pthread_cond_wait(&has_product, &lock);    //阻塞等待
        }  
        mp = head; 
        head = mp->next; //模拟消费掉一个产品
        pthread_mutex_unlock(&lock);
        printf("-Consume ---%d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }  
}
void *producer(void *p)
{
    struct msg *mp;
    while (1) {
        mp = (struct msg *)malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1; //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);
        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);
        pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }  
}
int main(int argc, char *argv[])
{
    pthread_t pid, cid;
    srand(time(NULL));
    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}     

四、信号量

介绍:
                进化版的互斥锁(1 --> N)
                它可以有多个初值: 信号量的初值,决定了占用信号量的线程的个数。
   
优势: (什么意思)
                
        由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办 法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
        

1.sem_init 函数

sem_init 函数 
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参 1:sem 信号量
参 2:pshared 取 0 用于线程间;取非 0(一般为 1)用于进程间
参 3:value 指定信号量初值

2.sem_destroy 函数

销毁一个信号量
        int sem_destroy(sem_t *sem);

3.sem_wait 函数

给信号量加锁 --
    int sem_wait(sem_t *sem);

4.sem_post 函数

给信号量解锁 ++
int sem_post(sem_t *sem);

5.sem_trywait 函数

尝试对信号量加锁 -- (与 sem_wait 的区别类比 lock 和 trylock)
int sem_trywait(sem_t *sem);

6.sem_timedwait 函数

限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参 2:abs_timeout 采用的是绝对时间。
定时 1 秒:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义 timespec 结构体变量 t
t.tv_sec = cur+1; 定时 1 秒
t.tv_nsec = t.tv_sec +100; 
sem_timedwait(&sem, &t); 传参

借助信号量实现生产者消费者模型

实现思路:用两个信号量用来表示产品数 和 空格数

                然后进行制约同步协调

#define NUM 5
int queue[NUM]={0};       //共享数据区
sem_t blank_number;         //信号量 1
sem_t product_number;       //信号量 2
void* producer(void*arg)
{
    int i=0;
    int ret=-1;
    while(1)
    {  
       if( sem_wait(&blank_number) !=0 ) { printf("producer sem_wait error\n") ; return 0;}                 //P blank_number
       queue[i]=rand()%1000+1;
       printf("---produce---%d \n",queue[i]);
       if( sem_post(&product_number) != 0) { printf("producer sem_post errpr \n") ; return 0; }               //V product_number
       i=(i+1)%NUM;
       sleep(rand()%1);
    }  
}
void* consumer(void*arg)
{
    int i=0;
    int ret=-1;
    while(1)
    {  
       if( sem_wait(&product_number) !=0 ) { printf("consumer sem_wait error\n") ; return 0;}                 //P blank_number
        printf("--consumer--%d \n",queue[i]);
        queue[i]=0;
       if( sem_post(&blank_number) != 0) { printf("consumer sem_post errpr\n") ; return 0; }               //V product_number

        i=(i+1)%NUM;
        sleep(rand()%3);
    }  
}
int main()
{
    printf("\n本程序演示用信号量实现生产者消费者模型 \n");

    pthread_t pro_pthreadid=-1;
    pthread_t con_pthreadid=-1;

    if( sem_init(&blank_number,0,NUM) !=0) {printf("sem_init error\n"); return 0;};  
    if( sem_init(&product_number,0,NUM) !=0) {printf("sem_init error\n"); return 0;};  

    if( pthread_create(&pro_pthreadid,NULL,producer,NULL) !=0) { printf("pthread_create error\n"); return 0; }  
    if( pthread_create(&pro_pthreadid,NULL,consumer,NULL) !=0) { printf("pthread_create error\n"); return 0; }

    if( pthread_join(con_pthreadid,NULL) != 0) {printf("pthread_join error\n"); return 0;}   
    if( pthread_join(pro_pthreadid,NULL) != 0) {printf("pthread_join error\n"); return 0;}  

    sem_destroy(&blank_number);
    sem_destroy(&product_number);                                                                                                                                                                    

    return 0; 
}

疑问:段错误

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值