《Linux操作系统 - 高级编程》第二部分 进程(第2章 线程基础B)

2.4多线程的同步与互斥

2.4.1为什么要用多线程技术

【1】避免阻塞,大家知道,单个进程只有一个主线程,当主线程阻塞的时候,整个进程也就阻塞了,无法再去做其它的一些功能了。
【2】避免CPU空转,应用程序经常会涉及到RPC,数据库访问,磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应时,CPU却不能去处理新的请求,导致这种【3】提升效率,一个进程要独立拥有4GB的虚拟地址空间,而多个线程可以共享同一地址空间,线程的切换比进程的切换要快得多。

2.4.2 如何使用多线程技术进行编程

下面给出个多线程程序,一个最简单的模拟售票系统,代码如下:

#include <stdio.h>  
#include <pthread.h>  
  
void *ticketsell1(void *);  
void *ticketsell2(void *);  
int tickets = 20;  
  
int main()  
{  
    pthread_t id1,id2;  
    int error;  
  
    error = pthread_create(&id1, NULL, ticketsell1, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    error = pthread_create(&id2, NULL, ticketsell2, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    pthread_join(id1,NULL);  
    pthread_join(id2,NULL);  
      
    return 0;  
}  
  
void *ticketsell1(void *arg)  
{  
    while(1)  
    {  
        if(tickets > 0)  
        {  
//          usleep(1000);  
            printf("ticketse1 sells ticket:%d\n",tickets--);  
        }  
        else  
        {  
            break;  
        }  
    }  
    return (void *)0;  
}  
  
void *ticketsell2(void *arg)  
{  
    while(1)  
    {  
        if(tickets > 0)  
        {  
//          usleep(1000);  
            printf("ticketse2 sells ticket:%d\n",tickets--);  
        }  
        else  
        {  
            break;  
        }  
    }  
  
    return (void *)0;  
}  

执行结果如下:
这里写图片描述
看到结果,我们发现时能正常卖票的,一部分连续是sel2,另一部分是ticketsel1;此时,其实存在一个隐含的问题,就是线程间的切换,在单CPU系统中,CPU是有时间片时间,时间片到了,就要执行其它的线程,假设thread1执行到if里面,但在printf执行前发生了线程切换,那么会发生什么呢?我们在这里用usleep函数(放开程序中的usleep注释行)进行强制模拟切换;
我们看看结果:
这里写图片描述
运行程序发现竟然有0号票被卖出了,这显然是错误的!当thread1的if里面发生线程切换时,thread2得到运行,把最后一张票卖了,此时thread1恢复运行,结果卖出了0号票,这里我们需要的是火车票的票数数据对于所有线程而言是同步的,所以就要用到线程同步技术了。

2.4.3使用多线程的同步与互斥

1、多线程的同步方式有很多种,例如互斥锁,条件变量,信号量,读写锁。先看看互斥锁如何解决多线程之间的同步问题。程序用互斥锁后如下:

#include <stdio.h>  
#include <pthread.h>  
  
void *ticketsell1(void *);  
void *ticketsell2(void *);  
int tickets = 20;  
pthread_mutex_t mutex;  
  
int main()  
{  
    pthread_t id1,id2;  
    pthread_mutex_init(&mutex, NULL);//  
    int error;  
  
    error = pthread_create(&id1, NULL, ticketsell1, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    error = pthread_create(&id2, NULL, ticketsell2, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    pthread_join(id1,NULL);  
    pthread_join(id2,NULL);  
      
    return 0;  
}  
  
void *ticketsell1(void *arg)  
{  
    while(1)  
    {  
        pthread_mutex_lock(&mutex);//给互斥量上锁  
        if(tickets > 0)  
        {  
            usleep(1000);  
            printf("ticketse1 sells ticket:%d\n",tickets--);  
            pthread_mutex_unlock(&mutex);//给互斥量解锁  
              
        }  
        else  
        {  
            pthread_mutex_unlock(&mutex);//给互斥量解锁  
            break;  
        }  
        pthread_yield();//线程调度函数,使每个线程都有执行机会  
    }  
    return (void *)0;  
}  
  
void *ticketsell2(void *arg)  
{  
    while(1)  
    {  
        pthread_mutex_lock(&mutex);//给互斥量上锁  
        if(tickets > 0)  
        {  
            usleep(1000);  
            printf("ticketse2 sells ticket:%d\n",tickets--);  
            pthread_mutex_unlock(&mutex);//给互斥量解锁  
        }  
        else  
        {  
            pthread_mutex_unlock(&mutex);//给互斥量解锁  
            break;  
        }  
        pthread_yield();//线程调度函数,是两个线程都有执行机会  
    }  
  
    return (void *)0;  
}  

执行结果如下:
$ gcc -o mthread1 mthread1.c -lpthread
$ ./mthread1
ticketse2 sells ticket:20
ticketse1 sells ticket:19
ticketse2 sells ticket:18
ticketse1 sells ticket:17
ticketse2 sells ticket:16
ticketse1 sells ticket:15
ticketse2 sells ticket:14
ticketse1 sells ticket:13
ticketse2 sells ticket:12
ticketse1 sells ticket:11
ticketse2 sells ticket:10
ticketse1 sells ticket:9
ticketse2 sells ticket:8
ticketse1 sells ticket:7
ticketse2 sells ticket:6
ticketse1 sells ticket:5
ticketse2 sells ticket:4
ticketse1 sells ticket:3
ticketse2 sells ticket:2
ticketse1 sells ticket:1

2、再看看用信号量来解决多线程的同步问题,程序代码如下:

#include <stdio.h>  
#include <pthread.h>  
#include <semaphore.h>  
  
void *ticketsell1(void *);  
void *ticketsell2(void *);  
int tickets = 20;  
sem_t mutex,full;  
  
int main()  
{  
    pthread_t id1,id2;  
    int error;  
    int ret;  
  
    ret = sem_init(&mutex, 0 ,1);//初始化mutex信号量为1  
    ret += sem_init(&full, 0 ,0);//初始化full信号量为0  
  
    if(ret != 0)  
    {  
        printf("sem_init fails!\n");  
    }  
  
    error = pthread_create(&id1, NULL, ticketsell1, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    error = pthread_create(&id2, NULL, ticketsell2, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    pthread_join(id1,NULL);  
    pthread_join(id2,NULL);  
      
    return 0;  
}  
  
void *ticketsell1(void *arg)  
{  
    while(1)  
    {  
        sem_wait(&mutex);//mutex信号量进行P操作  
        if(tickets > 0)  
        {  
            usleep(1000);  
            printf("ticketse1 sells ticket:%d\n",tickets--);  
            sem_post(&full);//full信号量进行V操作  
        }  
        else  
        {  
            sem_post(&full);//full信号量进行V操作  
            break;  
        }  
    }  
    return (void *)0;  
}  
  
void *ticketsell2(void *arg)  
{  
    while(1)  
    {  
        sem_wait(&full);//full信号量进行P操作  
        if(tickets > 0)  
        {  
            usleep(1000);  
            printf("ticketse2 sells ticket:%d\n",tickets--);  
            sem_post(&mutex);//mutex信号量进行V操作  
        }  
        else  
        {  
            sem_post(&mutex);//mutex信号量进行V操作  
            break;  
        }  
    }  
  
    return (void *)0;  
}  

执行结果:
$ gcc -o mthread1 mthread1.c -lpthread
$ ./mthread1
ticketse1 sells ticket:20
ticketse2 sells ticket:19
ticketse1 sells ticket:18
ticketse2 sells ticket:17
ticketse1 sells ticket:16
ticketse2 sells ticket:15
ticketse1 sells ticket:14
ticketse2 sells ticket:13
ticketse1 sells ticket:12
ticketse2 sells ticket:11
ticketse1 sells ticket:10
ticketse2 sells ticket:9
ticketse1 sells ticket:8
ticketse2 sells ticket:7
ticketse1 sells ticket:6
ticketse2 sells ticket:5
ticketse1 sells ticket:4
ticketse2 sells ticket:3
ticketse1 sells ticket:2
ticketse2 sells ticket:1

上面的sem_init函数用来初始化两个信号量的初始化值,这里一个设为1,一个设为0,sem_wait类似于P操作,让信号量减1,如果小于结果小于0,线程阻塞,否则线程继续执行,sem_post类似于V操作,提升信号量的值,加1,通过这两个信号量之间的互相“救对方”,就可以实现这两个线程的同步执行。
我们编译运行以上程序,发现两个售票点交替卖票,两个纯程依次得到机会执行,并且不会有0号票卖出,实现了同步。

3、我们再用条件变量来解决同步问题,一般条件变量需要结合互斥量一起使用,代码如下

#include <stdio.h>  
#include <pthread.h>  
#include <semaphore.h>  
  
void *ticketsell1(void *);  
void *ticketsell2(void *);  
int tickets = 20;  
pthread_mutex_t mutex;  
pthread_cond_t  qready = PTHREAD_COND_INITIALIZER;//静态初始化条件变量;  
          
int main()  
{  
    pthread_t id1,id2;  
    pthread_mutex_init(&mutex, NULL);  
    int error;  
  
    error = pthread_create(&id1, NULL, ticketsell1, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    error = pthread_create(&id2, NULL, ticketsell2, NULL);  
    if(error != 0)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
  
    pthread_join(id1,NULL);  
    pthread_join(id2,NULL);  
      
    return 0;  
}  
  
void *ticketsell1(void *arg)  
{  
    pthread_mutex_lock(&mutex);  
    while(tickets > 0)  
    {  
        if(tickets%2 == 1)  
        {  
            usleep(1000);  
            printf("ticketse1 sells ticket:%d\n",tickets--);  
            pthread_cond_signal(&qready);//条件改变,发送信号,通知ticketse2  
        }  
        else  
        {  
            pthread_cond_wait(&qready,&mutex);//解开Mutex,并等待qready改变  
        }  
        pthread_mutex_unlock(&mutex);//给互斥量解锁  
    }  
    return (void *)0;  
}  
  
void *ticketsell2(void *arg)  
{  
    pthread_mutex_lock(&mutex);  
    while(tickets > 0)  
    {  
        if(tickets%2 == 0)  
        {  
            usleep(1000);  
            printf("ticketse2 sells ticket:%d\n",tickets--);  
            pthread_cond_signal(&qready);//条件改变,发送信号,通知ticketse1  
        }  
        else  
        {  
            pthread_cond_wait(&qready,&mutex);//解开mutex,并等待qready改变  
        }  
        pthread_mutex_unlock(&mutex);//给互斥量解锁  
    }  
  
    return (void *)0;  
}  

执行结果如下:
$ gcc -o mthread1 mthread1.c -lpthread
$ ./mthread1
ticketse2 sells ticket:20
ticketse1 sells ticket:19
ticketse2 sells ticket:18
ticketse1 sells ticket:17
ticketse2 sells ticket:16
ticketse1 sells ticket:15
ticketse2 sells ticket:14
ticketse1 sells ticket:13
ticketse2 sells ticket:12
ticketse1 sells ticket:11
ticketse2 sells ticket:10
ticketse1 sells ticket:9
ticketse2 sells ticket:8
ticketse1 sells ticket:7
ticketse2 sells ticket:6
ticketse1 sells ticket:5
ticketse2 sells ticket:4
ticketse1 sells ticket:3
ticketse2 sells ticket:2
ticketse1 sells ticket:1

条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件变量发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

函数pthread_cond_wait使线程阻塞在一个条件变量上,而函数pthread_cond_signal是用来释放被阻塞在条件变量上的一个线程。但是要注意的是,条件变量只是起到阻塞和唤醒线程的作用,具体的判断条件还需用户给出,我这里给出的是tickets是否是偶数这个条件。

2.5 Linux多线程编程实例解析

Linux系统下的多线程遵循POSIX线程接口,称为 pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux 下pthread的实现是通过系统调用clone()来实现的。clone()是 Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的 多线程程序 pthread_create.c。
一个重要的线程创建函数原型:

 #include <pthread.h>
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);

返回值:若是成功建立线程返回0,否则返回错误的编号
形式参数:
pthread_t *restrict tidp 要创建的线程的线程id指针
const pthread_attr_t restrict attr 创建线程时的线程属性
void
(start_rtn)(void) 返回值是void类型的指针函数
void *restrict arg start_rtn的行参
例程1:
功能:创建一个简单的线程
程序名称:pthread_create.c
代码如下:

#include <stdio.h>  
#include <pthread.h>  
  
void *mythread1(void)  
{  
    int i;  
    for(i = 0; i < 10; i++)  
    {  
        printf("This is the 1st pthread,created by xiaoqiang!\n");  
        sleep(1);  
    }  
}  
  
void *mythread2(void)  
{  
    int i;  
    for(i = 0; i < 10; i++)  
    {  
        printf("This is the 2st pthread,created by xiaoqiang!\n");  
        sleep(1);  
    }  
}  
  
int main(int argc, const char *argv[])  
{  
    int i = 0;  
    int ret = 0;  
    pthread_t id1,id2;  
  
    ret = pthread_create(&id1, NULL, (void *)mythread1,NULL);  
    if(ret)  
    {  
        printf("Create pthread error!\n");  
        return 1;  
    }  
    ret = pthread_create(&id2, NULL, (void *)mythread2,NULL);  
    if(ret)  
    {  
        printf("Create pthread error!\n");  
        return 1;  
    }  
      
    pthread_join(id1,NULL);  
    pthread_join(id2,NULL);  
  
    return 0;  
}  

执行结果如下:
$ gcc -o thread1 thread1.c -lpthread
$ ./thread1
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 2st pthread,created by xiaoqiang!
This is the 1st pthread,created by xiaoqiang!

两个线程交替执行。另外,因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过,会出现下面错误
thread_test.c: 在函数 ‘create’ 中:
thread_test.c:7: 警告: 在有返回值的函数中,程序流程到达函数尾
/tmp/ccOBJmuD.o: In function `main’:thread_test.c:(.text+0x4f):对‘pthread_create’未定义的引用
collect2: ld 返回 1
此例子介绍了创建线程的方法,下面例子介绍向线程传递参数。
例程2:
功能:向新的线程传递整形值
程序名称:pthread_int.c
代码如下:

#include <stdio.h>  
#include <pthread.h>  
  
void *create(void *arg)  
{  
    int *num;  
    num = (int *)arg;  
    printf("Create parameter is %d\n",*num);  
    return (void *)0;  
}  
  
int main(int argc, const char *argv[])  
{  
    pthread_t id1;  
    int error;  
  
    int test = 4;  
    int *attr = &test;  
  
    error = pthread_create(&id1,NULL,create,(void *)attr);  
  
    if(error)  
    {  
        printf("Pthread_create is not created!\n");  
        return -1;  
    }  
    sleep(1);  
  
    printf("Pthread_create is created..\n");  
    return 0;  
}  

执行结果如下:
$ gcc -o thread2 thread2.c -lpthread
$ ./thread2
Create parameter is 4
Pthread_create is created…
例程总结:
可以看出来,我们在main函数中传递的整行指针,传递到我们新建的线程函数中。
在上面的例子可以看出来我们向新的线程传入了另一个线程的int数据,线程之间还可以传递字符串或是更复杂的数据结构。
例程3:
程序功能:向新建的线程传递字符串
程序名称:pthread_string.c
代码如下:

#include <stdio.h>  
#include <pthread.h>  

void *create(char *arg)  
{  
    char *str;  
    str = arg;  
    printf("The parameter passed from main is %s\n",str);  
  
    return (void *)0;  
}  
int main()  
{  
    int error;  
    pthread_t id1;  
    char *str1 = "Hello !";  
    char *attr = str1;  
    error = pthread_create(&id1, NULL, create, (void *)attr);  
  
    if(error != 0)  
    {  
        printf("This pthread is not created!\n");  
        return -1;  
    }  
    sleep(1);  
  
    printf("pthread is created..\n");  
    return 0;  
} 

执行结果如下:
The parameter passed from main is Hello !
pthread is created…
例程总结:
可以看出来main函数中的字符串传入了新建的线程中。

例程4:
程序功能:向新建的线程传递字符串
程序名称:pthread_struct.c
代码如下:

#include <stdio.h>  
#include <pthread.h>  
#include <stdlib.h>  
  
struct menber  
{  
    int a;  
    char *s;  
};  
  
void *create(void *arg)  
{  
    struct menber *temp;  
    temp = (struct menber *)arg;  
    printf("menber->a = %d\n",temp->a);  
    printf("menber->s = %s\n",temp->s);  
  
    return (void *)0;  
}  
  
int main()  
{  
    int error;  
    pthread_t id1;  
    struct menber *p;  
    p = (struct menber *)malloc(sizeof(struct menber));  
    p->a = 1;  
    p->s = "xiaoqiang!";  
  
    error = pthread_create(&id1,NULL,create,(void *)p);  
  
    if(error)  
    {  
        printf("pthread is not created!\n");  
        return -1;  
    }  
    sleep(1);  
    printf("pthread is created!\n");  
  
    free(p);  
    p = NULL;  
    return 0;  
}  

执行结果如下:
$ gcc -o thread4 thread4.c -lpthread
$ ./thread4
menber->a = 1
menber->s = xiaoqiang!
pthread is created!

例程总结:
可以看出来main函数中的一个结构体传入了新建的线程中。
线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的,包括文本程序、程序的全局内存和堆内存、栈以及文件描述符
例程5:
程序目的:验证新建立的线程可以共享进程中的数据
程序名称:pthread_share.c
代码如下:

#include <stdio.h>  
#include <pthread.h>  
  
static int a = 5;  
  
void *create(void *arg)  
{  
    printf("New pthread...\n");  
    printf("a = %d\n",a);  
  
    return (void *)0;  
}  
int main(int argc, const char *argv[])  
{  
    int error;  
    pthread_t id1;  
    error = pthread_create(&id1, NULL, create, NULL);  
    if(error != 0)  
    {  
        printf("new thread is not created!\n");  
        return -1;  
    }  
    sleep(1);  
    printf("New thread is created...\n");  
  
    return 0;  
}  

结果如下:
$ gcc -o thread5 thread5.c -lpthread
$ ./thread5
New pthread…
a = 5
New thread is created…

例程总结:
可以看出来,我们在主线程更改了我们的全局变量a的值的时候,我们新建立的线程则打印出来了改变的值,可以看出可以访问线程所在进程中的数据信息。
线程的终止
如果进程中任何一个线程中调用exit,_Exit,或者是_exit,那么整个进程就会终止,
与此类似,如果信号的默认的动作是终止进程,那么,把该信号发送到线程会终止进程。
线程的正常退出的方式:
(1) 线程只是从启动例程中返回,返回值是线程中的退出码
(2) 线程可以被另一个进程进行终止
(3) 线程自己调用pthread_exit函数
两个重要的函数原型:

include <pthread.h>  
void pthread_exit(void *rval_ptr);  
/*rval_ptr 线程退出返回的指针*/  
int pthread_join(pthread_t thread,void **rval_ptr);  
   /*成功结束进程为0,否则为错误编码*/  

pthread_join使一个线程等待另一个线程结束。
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
头文件 : #include <pthread.h>
函数定义: int pthread_join(pthread_t thread, void **retval);
描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。

例程6
程序目的:线程正常退出,接受线程退出的返回码
程序名称:pthread_exit.c
执行代码如下:

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h>  
  
void *create(void *arg)  
{  
    printf("new thread is created ... \n");  
    return (void *)0;  
}  
  
int main(int argc,char *argv[])  
{  
    pthread_t tid;  
    int error;  
    void *temp;  
  
    error = pthread_create(&tid, NULL, create, NULL);  
  
    if( error )  
    {  
        printf("thread is not created ... \n");  
        return -1;  
    }  
    error = pthread_join(tid, &temp);  
  
    if( error )  
    {  
        printf("thread is not exit ... \n");  
        return -2;  
    }  
  
    printf("thread is exit code %d \n", (int )temp);  
                                          
    return 0;  
}  

执行结果如下:
$ gcc -o thread6 thread6.c -lpthread
$ ./thread6
new thread is created …
thread is exit code 0

例程总结:
可以看出来,线程退出可以返回线程的int数值。线程退出不仅仅可以返回线程的int数值,还可以返回一个复杂的数据结构
例程7
程序目的:线程结束返回一个复杂的数据结构
代码如下:

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h>  
  
struct menber  
{  
    int a;  
    char *b;  
}temp={8,"xiaoqiang"};  
  
void *create(void *arg)  
{  
    printf("new thread ... \n");  
    return (void *)&temp;  
}  
  
int main(int argc,char *argv[])  
{  
    int error;  
    pthread_t tid;  
    struct menber *c;  
  
    error = pthread_create(&tid, NULL, create, NULL);  
  
    if( error )  
    {  
        printf("new thread is not created ... \n");  
        return -1;  
    }  
    printf("main ... \n");  
  
    error = pthread_join(tid,(void *)&c);  
  
    if( error )  
    {  
        printf("new thread is not exit ... \n");  
        return -2;  
    }  
    printf("c->a = %d  \n",c->a);  
    printf("c->b = %s  \n",c->b);  
    sleep(1);  
    return 0;  
}  

执行结果如下:
$ gcc -o thread7 thread7.c -lpthread
$ ./thread7
main …
new thread …
c->a = 8
c->b = xiaoqiang
例程总结:
一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用,如果数据结构已经发生变化,那返回的就不会是我们所需要的,而是脏数据。

线程标识
函数原型:

#include <pthread.h>
pthread_t pthread_self(void);
pid_t getpid(void);

getpid()用来取得目前进程的进程识别码,函数说明

例程8
程序目的:实现在新建立的线程中打印该线程的id和进程id
代码如下:

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h> /*getpid()*/  
  
void *create(void *arg)  
{  
    printf("New thread .... \n");  
    printf("This thread's id is %u  \n", (unsigned int)pthread_self());  
    printf("The process pid is %d  \n",getpid());  
    return (void *)0;  
}  
  
int main(int argc,char *argv[])  
{  
    pthread_t tid;  
    int error;  
  
    printf("Main thread is starting ... \n");  
  
    error = pthread_create(&tid, NULL, create, NULL);  
  
    if(error)  
    {  
        printf("thread is not created ... \n");  
        return -1;  
    }  
    printf("The main process's pid is %d  \n",getpid());  
    sleep(1);  
    return 0;  
}  

执行结果如下:
$ gcc -o thread8 thread8.c -lpthread
$ ./thread8
Main thread is starting …
The main process’s pid is 4955
New thread …
This thread’s id is 3075853120
The process pid is 4955

##2.6多线程经典问题(生产者-消费者)
“生产者——消费者”问题是Linux多线程编程中的经典问题,主要是利用信号量处理线程间的同步和互斥问题。
“生产者——消费者”问题描述如下:
有一个有限缓冲区(这里用有名管道实现 FIFO 式缓冲区)和两个线程:生产者和消费者,它们分别不停地把产品放入缓冲区中拿走产品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也不IXUS等待。另外,因为缓冲区是临界资源,所以生产者和消费者之间必须互斥进行。它们之间的关系如下:

这里写图片描述

图1
这里要求使用有名管道来模拟有限缓冲区,并用信号量来解决“生产者——消费者”问题中的同步和互斥问题。 ### 2.6.1信号量分析 这里使用3个信号量,其中两个信号量 avail 和 full 分别用于解决生产者和消费者线程之间的互斥问题。其中avail 表示缓冲区的空单元数,初始值为N;full 表示缓冲区非空单元数,初始值为 0 ; mutex 是互斥信号量 ,初始值为 1(当然也可以用互斥锁来实现互斥操作)。 ### 2.6.2画出流程图

这里写图片描述

图2
### 2.6.3编写代码 本实验的代码中缓冲区拥有3个单元,每个单元为5个字节。为了尽量体现每个信号量的意义,在程序中生产过程和消费过程是随机(采取0~5s 的随机事件间隔)进行的,而且生产者的速度比消费者的速度平均快两倍左右。生产者一次生产一个单元的产品(放入hello字符串),消费者一次消费一个单元的产品。
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <pthread.h>  
#include <sys/types.h>  
#include <time.h>  
#include <fcntl.h>  
#include <semaphore.h>  
#include <sys/ipc.h>  
#include <errno.h>  
#define MYFIFO "myfifo"  
#define BUFFER_SIZE 3  
#define UNIT_SIZE 5  
#define RUN_TIME 30  
#define DELAY_TIME_LEVELS 5.0  
  
void *producer(void *arg);  
void *customer(void *arg);  
  
int fd;  
time_t end_time;  
sem_t mutex,full,avail;  
  
int main()  
{  
    int ret;  
    pthread_t thrd_prd_id,thrd_cst_id;  
  
    srand(time(NULL));  
    end_time = time(NULL) + RUN_TIME;  
  
        /*创建有名管道*/  
    if((mkfifo(MYFIFO,0644) < 0) && (errno != EEXIST))  
    {  
        perror("mkfifo error!");  
        exit(-1);  
    }  
  
        /*打开管道*/  
    fd = open(MYFIFO,O_RDWR);  
    if(fd == -1)  
    {  
        perror("open fifo error");  
        exit(-1);  
    }  
  
        /*初始化互斥信号量为1*/  
    ret = sem_init(&mutex,0,1);  
        /*初始化avail信号量为 N */  
    ret += sem_init(&avail,0,BUFFER_SIZE);  
    /*初始化full信号量为0*/  
    ret += sem_init(&full,0,0);  
  
    if(ret != 0)  
    {  
        perror("sem_init error");  
        exit(-1);  
    }  
  
        /*创建两个线程*/  
    ret = pthread_create(&thrd_prd_id,NULL,producer,NULL);  
    if(ret != 0)  
    {  
        perror("producer pthread_create error");  
        exit(-1);  
    }  
  
    ret = pthread_create(&thrd_cst_id,NULL,customer,NULL);  
    if(ret != 0)  
    {  
        perror("customer pthread_create error");  
        exit(-1);  
    }  
  
    pthread_join(thrd_prd_id,NULL);  
    pthread_join(thrd_cst_id,NULL);  
    close(fd);  
    unlink(MYFIFO);  
  
    return 0;  
}  
void *producer(void *arg) //生产者线程  
{  
    int real_write;  
    int delay_time;  
  
    while(time(NULL) < end_time)  
    {  
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/RAND_MAX/2.0) + 1;  
        sleep(delay_time);  
  
                /*P操作信号量avail和mutex*/  
        sem_wait(&avail);  
        sem_wait(&mutex);  
        printf("\nproducer have delayed %d seconds\n",delay_time);  
  
                /*生产者写入数据*/  
        if((real_write = write(fd,"hello",UNIT_SIZE)) == -1)  
        {  
            if(errno == EAGAIN)  
            {  
                printf("The buffer is full,please wait for reading!\n");  
            }  
        }  
        else  
        {  
            printf("producer writes %d bytes to the FIFO\n",real_write);          
            printf("Now,the buffer left %d spaces!\n",avail);  
        }  
                /*V操作信号量full 和 mutex*/  
        sem_post(&full);  
        sem_post(&mutex);  
    }  
    pthread_exit(NULL);  
}  
  
void *customer(void *arg) //消费者线程  
{  
    unsigned char read_buffer[UNIT_SIZE];  
    int real_read;  
    int delay_time;  
  
    while(time(NULL) < end_time)  
    {  
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/RAND_MAX/2.0) + 1;  
        sleep(delay_time);  
  
        sem_wait(&full); //P操作信号量full和mutex  
        sem_wait(&mutex);  
        memset(read_buffer,0,UNIT_SIZE);  
        printf("\nCustomer have delayed %d seconds\n",delay_time);  
  
        if((real_read = read(fd,read_buffer,UNIT_SIZE)) == -1)  
        {  
            if(errno == EAGAIN)  
            {  
                printf("The buffer is empty,please wait for writing!\n");  
            }  
        }  
        else  
        {  
            printf("customer reads %d bytes from the FIFO\n",real_read);          
        }  
  
        sem_post(&avail); //V操作信号量 avail 和 mutex  
        sem_post(&mutex);         
    }  
  
    pthread_exit(NULL);  
}  

执行结果如下:
$ ./cust_prod
producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 3 seconds
customer reads 5 bytes from the FIFO

producer have delayed 3 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 1 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

Customer have delayed 1 seconds
customer reads 5 bytes from the FIFO

producer have delayed 3 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 1 seconds
customer reads 5 bytes from the FIFO

producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 1 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 1 spaces!

Customer have delayed 3 seconds
customer reads 5 bytes from the FIFO

Customer have delayed 1 seconds
customer reads 5 bytes from the FIFO

producer have delayed 3 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

producer have delayed 1 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 1 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

Customer have delayed 1 seconds
customer reads 5 bytes from the FIFO

producer have delayed 3 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

Customer have delayed 2 seconds
customer reads 5 bytes from the FIFO

producer have delayed 2 seconds
producer writes 5 bytes to the FIFO
Now,the buffer left 2 spaces!

本章参考代码

点击进入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bruceoxl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值