C语言多线程实例之pthread的应用(在windows下的应用(win7))

Pthread是由POSIX提出的一套通用的线程库,在linux平台下,它被广泛的支持,而windows平台下,却并不被支持,而pthreads-w32为我们提供了解决方案,本文我们准备在我们的windows平台下进行pthread-w32的安装,在网络上有类似的文章,但是讲的都是比较老的平台,在windows8下支持并不全面,不过可以作为参考。我们在这里贴出几个网址,供参考使用。

   Windows 7 64bit和Visual Studio 2010下安装及使用Pthread-w32 2.8 

   windows下使用pthread库(转)

    如果你的是XP系统或者win7 32位系统,那么,那两篇文章已经足以你完成pthread-w32的安装了。现在,我们开始讲我们的尝试过程。

一、安装平台

    windows8 64位系统,Microsoft Visual Studio 2012

二、pthreads-w32 下载地址

    我们这里下载最新版本pthreads-w32-2-9-1

ftp://sourceware.org/pub/pthreads-win32/pthreads-w32-2-9-1-release.zip

下载后解压,可以看到共有三个文件夹

我们用到的主要是“Pre-built.2”这个文件夹下的三个文件夹,分别是动态链接库、头文件、静态链接库


三、配置头文件及静态链接库

    这里有多种方式,我们这里只提到我们用到的一种,总之目的是让我们建立的工程能够找到对应的头文件、静态库文件,以及运行时程序能够找到动态链接库文件。

这里,我们直接把头文件拷贝到Visual Studio的默认路径的头文件中,即把include文件夹中的三个文件直接拷贝到Visual Studio安装目录下VC->include文件夹下,例如我将include中文件拷贝到的位置是

E:\Program Files\Microsoft Visual Studio 11.0\VC\include

这样,我们就不必每次在项目用到时都配置一遍,特别是在Visual Studio2012 貌似不支持全局的头文件配置时(不确定,如果谁找到了可以告诉我一声),这种方式对于经常会建一些小项目的人来说,相对节省时间。

同样的办法与原因,我们也可以把lib文件夹下的内容拷贝到Visual Studio安装目录下默认的lib寻找路径中,即VC->lib中,例如我将lib文件夹下的x64与x86两个文件直接拷贝到

    E:\Program Files\Microsoft Visual Studio 11.0\VC\lib

的下面。


四、配置动态链接库

   和头文件和静态链接库的配置方式相似,我们这里将dll文件夹的内容放到我们程序能够找到的位置,我们的方案是

把dll下的x64文件夹下的两个文件,即pthreadGC2.dll与pthreadVC2.dll拷贝到C:\Windows\System32下(用于64位程序的运行)

把dll下的x86文件夹下的五个文件,拷贝到C:\Windows\SysWOW64下(用于32位程序的运行),注意一下,千万不能将这些文件拷贝反位置,否则,程序运行时会提示说找不到对应的dll文件。这些在网上的很多文章中都被忽略掉了,所以我们特别提出。


五:注意事项:

1)在linux下编译的时候要加上-lpthread ,如:

gcc -g thread_test.c -o thread_test -lpthread

2)按上述步骤如果出错,则把lib文件夹下x86中的文件直接拷贝到上一级目录。我的问题解决了。


六:列两个多线程的实例:

/*mutex.c*/
#include 
#include 
#include 
#include 
/*全局变量*/
int gnum = 0;
/*互斥量 */
pthread_mutex_t mutex;
/*声明线程运行服务程序*/
static void pthread_func_1 (void);
static void pthread_func_2 (void);

int main (void)
{
 /*线程的标识符*/
  pthread_t pt_1 = 0;
  pthread_t pt_2 = 0;
  int ret = 0;
  /*互斥初始化*/
  pthread_mutex_init (&mutex, NULL);
  /*分别创建线程1、2*/
  ret = pthread_create( &pt_1,                  //线程标识符指针
                                     NULL,                  //默认属性
                                     (void *)pthread_func_1,//运行函数
                                     NULL);                  //无参数
  if (ret != 0)
  {
     perror ("pthread_1_create");
  }

  ret = pthread_create( &pt_2,                  //线程标识符指针
                                     NULL,                   //默认属性
                                     (void *)pthread_func_2, //运行函数
                                     NULL);                  //无参数
  if (ret != 0)
  {
     perror ("pthread_2_create");
  }
  /*等待线程1、2的结束*/
  pthread_join (pt_1, NULL);
  pthread_join (pt_2, NULL);

  printf ("main programme exit!/n");
  return 0;
}
/*线程1的服务程序*/
static void pthread_func_1 (void)
{
  int i = 0;

  for( i=0; i<3; i++ ){
    printf ("This is pthread_1!/n");
    pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
    sleep (1);
    /*临界资源*/
    gnum++;
    printf ("Thread_1 add one to num:%d/n",gnum);
    pthread_mutex_unlock(&mutex); /*释放互斥锁*/
  }

  pthread_exit ( NULL );
}
/*线程2的服务程序*/
static void pthread_func_2 (void)
{
  int i = 0;

  for( i=0; i<5; i++ )  {
    printf ("This is pthread_2!/n");
    pthread_mutex_lock(&mutex); /*获取互斥锁*/
        /*注意,这里以防线程的抢占,以造成一个线程在另一个线程sleep时多次访问互斥资源,所以sleep要在得到互斥锁后调用*/
    sleep (1);
    /*临界资源*/
    gnum++;
    printf ("Thread_2 add one to num:%d/n",gnum);
    pthread_mutex_unlock(&mutex); /*释放互斥锁*/

  }

  pthread_exit ( NULL );
} 

有两类函数在这里说明一下:

第一类是互斥锁:

linux下为了多线程同步,通常用到锁的概念。
posix下抽象了一个锁类型的结构:ptread_mutex_t。通过对该结构的操作,来判断资源是否可以访问。顾名思义,加锁(lock)后,别人就无法打开,只有当锁没有关闭(unlock)的时候才能访问资源。

即对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。

  要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。

1.函数原型:

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

该函数用于C函数的多线程编程中,互斥锁的初始化。

  pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。

  pthread_mutexattr_init()函数成功完成之后会返回零,其他任何返回值都表示出现了错误。

  函数成功执行后,互斥锁被初始化为锁住态。

2. 互斥锁属性

互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:

  * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

  * PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

  * PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

  * PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

3. 其他锁操作

  锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

   int pthread_mutex_lock(pthread_mutex_t *mutex)

  int pthread_mutex_unlock(pthread_mutex_t *mutex)

  int pthread_mutex_trylock(pthread_mutex_t *mutex)

  pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

  1. 死锁:

      死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。

      总体来讲, 有几个不成文的基本原则:

      对共享资源操作前一定要获得锁。

      完成操作以后一定要释放锁。

      尽量短时间地占用锁。

      如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

      线程错误返回时应该释放它所获得的锁。


第二类是多线程:

pthread_create函数

原型:int  pthread_create((pthread_t  *thread, 
                             pthread_attr_t  *attr, 
                             void  *(*start_routine)(void  *), 
                             void  *arg)

用法:#include  

功能:创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。

说明:thread:线程标识符;

          attr:线程属性设置;

          start_routine:线程函数的起始地址;

          arg:传递给start_routine的参数;

          返回值:成功,返回0;出错,返回-1。

pthread_join函数

函数原型:int pthread_join(pthread_t tid, void **status);


功能:pthread_join()函数会一直阻塞调用线程,直到指定的线程tid终止。当pthread_join()返回之后,应用程序可回收

与已终止线程关联的任何数据存储空间,(另外也可设置线程attr属性,当线程结束时直接回收资源)如果没有必要等待特定的线程

终止之后才进行其他处理,则应当将该线程分离pthread_detach()。


头文件:#include 

pthread非linux系统的默认库, 需手动链接-线程库 -lpthread


参数:

tid:需要等待的线程,指定的线程必须位于当前的进程中,而且不得是分离线程

status:线程tid所执行的函数start_routine的返回值(start_routine返回值地址需要保证有效),其中status可以为nullptr


返回值:

调用成功完成后,pthrea_join()返回0.其他任何返回值都表示出现了错误。如果检测到以下任意情况

pthread_join()将失败并返回相应的值

ESRCH
描述: 没有找到与给定的线程ID 相对应的线程。(如果多个线程等待同一个线程终止,则所有等待线程将一直等到目标线程终止。

然后一个等待线程成功返回。其余的等待线程将失败返回ESRCH错误)
EDEADLK
描述: 将出现死锁,如一个线程等待其本身,或者线程A和线程B 互相等待。
EINVAL
描述: 与给定的线程ID 相对应的线程是分离线程。

pthread_exit函数

  原型:void  pthread_exit(void  *retval)

用法:#include  

功能:使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数(下篇博客中讲到)来同步并释放资源。

说明:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来检索获取。

举例:

/*thread.c*/  
#include   
#include   

/*线程一*/  
void thread_1(void)  
{  
    int i=0;  
    for(i=0;i<=6;i++)  
    {  
        printf("This is a pthread_1.\n");  
        if(i==2)  
            pthread_exit(0);                      //用pthread_exit()来调用线程的返回值,用来退出线程,但是退出线程所占用的资源不会随着线程的终止而得到释放  
        sleep(1);  
    }  
}  

/*线程二*/  
void thread_2(void)  
{  
    int i;  
    for(i=0;i<3;i++)  
        printf("This is a pthread_2.\n");           
    pthread_exit(0);                              //用pthread_exit()来调用线程的返回值,用来退出线程,但是退出线程所占用的资源不会随着线程的终止而得到释放  
}  

int main(void)  
{  
    pthread_t id_1,id_2;  
    int i,ret;  
/*创建线程一*/  
    ret=pthread_create(&id_1,NULL,(void  *) thread_1,NULL);  
    if(ret!=0)  
    {  
        printf("Create pthread error!\n");  
    return -1;  
    }  
/*创建线程二*/  
     ret=pthread_create(&id_2,NULL,(void  *) thread_2,NULL);  
    if(ret!=0)  
    {  
        printf("Create pthread error!\n");  
    return -1;  
    }  
/*等待线程结束*/  
    pthread_join(id_1,NULL);  
    pthread_join(id_2,NULL);  
    return 0;  
}  

pthread_create 传入参数:

pthread_create如何传递两个参数以上的参数
涉及多参数传递给线程的,都需要使用结构体将参数封装后,将结构体指针传给线程
定义一个结构体
struct mypara
{
var para1;//参数1
var para2;//参数2
}
将这个结构体指针,作为void *形参的实际参数传递
struct mypara pstru;
pthread_create(&ntid, NULL, thr_fn,& (pstru));
函数中需要定义一个mypara类型的结构指针来引用这个参数

void *thr_fn(void *arg)
{
mypara *pstru;
pstru = (struct mypara *) arg;
pstru->para1;//参数1
pstru->para2;//参数2
}

pthread_create函数接受的参数只有一个void
*型的指针,这就意味着你只能通过结构体封装超过一个以上的参数作为一个整体传递。这是pthread_create函数的接口限定的,别人已经明确表明
我只接受一个参数,你硬要塞给他两个肯定会出错了。所以通过结构体这种组合结构变通一下,同样实现了只通过一个参数传递,但通过结构指针对结构数据成员的
引用实现多参数的传递
这种用结构体封装多参数的用法不仅仅用在pthread_create函数中,如果你自己设计的函数需要的参数很多〉=5个以上,都可以考虑使用结构体封
装,这样对外你的接口很简洁清晰,你的函数的消费者使用起来也很方便,只需要对结构体各个成员赋值即可,避免了参数很多时漏传、误传(参数串位)的问题
结构体内包含结构体完全没有问题,很多应用都这么使用

举例:

static thread_func(void *arg)
{
    THREAD_CREATE_ARG *pMypara;
    pMypara = (THREAD_CREATE_ARG *)arg;

    //这里可以放线程要实现的功能;
        //e.g  func(pMypara->para1, pMypara->para2,pMypara->para3, ...);
}

pthread_t thread_id;

struct thread_create_arg THREAD_CREATE_ARG
{
    var para1;
    var para2;
    var para3;
    ....
}   

THREAD_CREATE_ARG thread_create_var;

pthread_create(&thread_id, NULL, (void *)thread_func, &thread_create_var); 

你可能感兴趣的:(学习笔记)