最近访客
暂无访客
暂无访客
详情
评论
问答

Linux系统编程—线程

文章最后更新时间:2024-10-20 20:04:22

一、线程与进程的区别

  1. 典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只能做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
  2. 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存)的基本单元。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
  3. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
  4. 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。进程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

二、使用线程的理由

  1. 与进程相比,它是一种非常“节俭”的多任务操作方式,内存开销小。
  2. 线程间通信方便。

三、关于线程的API

1、线程的创建

函数原型:

#include <pthread.h>

  //成功执行返回0,否则返回错误编码
  int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
  //Compile and link with -pthread.

参数说明:

thread:当pthread_create成功返回时,由thread指向的内存单元被设置为新创建线程的线程ID;

attr:用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程;

start_routine:函数指针,当创建线程成功会执行该函数,该函数只有一个无类型指针参数arg。如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

2、线程退出

函数原型:


#include <pthread.h>

void pthread_exit(void *retval);

参数说明:

retval:线程退出的状态值。如果想获取线程退出状态,在线程退出时,会将设定值返回给pthread_join函数中retval参数,否则,不想获取线程退出状态,就设置为NULL。

线程调用该函数会直接退出。

3、线程等待

函数原型:

#include <pthread.h>

//成功执行返回0,否则返回错误编码
 int pthread_join(pthread_t thread, void **retval);

参数说明:

thread:指定等待的线程;

retval:接收线程退出的状态返回值,不想通过 pthread_exit函数获取状态值就是设置为NULL。
调用这个函数会一直阻塞该线程,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。

4、编程实现线程的创建、等待

代码如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
 //                         void *(*start_routine) (void *), void *arg);

int cnt=0;

void *func1(void *arg)
{
  printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t1:param is :%d\n",*((int *)arg));
  while(1)
  {
           printf("t1:cnt=%d\n",cnt++);
            sleep(1);
  }
}

void *func2(void *arg)
{
  printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t2:param is :%d\n",*((int *)arg));
  while(1)
  {
       printf("t2:cnt=%d\n",cnt++);
       sleep(1);
  }
}

int main()
{

  int ret;
  int param=100;
  pthread_t t1;
  pthread_t t2;
  int *pret=NULL;

  //线程创建
  ret=pthread_create(&t1,NULL,func1,(void *)&param);
      pthread_create(&t2,NULL,func2,(void *)&param);
 if(ret==0)
 {
   printf("main craete t1 success\n");
 }
  printf("main:%ld thread is create\n",(unsigned long)pthread_self());
  while(1);
  {    printf("main cnt=%d\n",cnt);  }
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);
  return 0;

}

运行结果:

图片[1]- Linux系统编程—线程- 如烟笔记

pthread_self()用于获取线程的ID号。通过运行结果可以看,线程共享着数据段内存空间,同时也能看到线程运行速度不一致和顺序不唯一,谁先运行不知道,目前无法做到同步。

四、互斥锁相关的API

互斥量(mutex)从本质上来说是一把锁,在访问共享资源前进行加锁,在访问完成后释放锁。对互斥量进行加锁后,其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。

在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。

1、创建和销毁互斥锁

函数原型:


#include <pthread.h>

// 动态初始化,创建互斥锁,若成功返回0,否则返回错误编码
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
//或静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//销毁互斥锁, 若成功返回0,否则返回错误编码
int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

mutex:指定为pthread_mutex_t类型。创建成功会返回为互斥锁的标识符。

attr:要用默认的属性初始化互斥量,只需把attr设置为NULL。

2、加锁和解锁

函数原型:


#include <pthread.h>
//加锁,若成功返回0,否则返回错误编码
int pthread_mutex_lock(pthread_mutex_t *mutex);
//尝试对互斥量进行加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//解锁,若成功返回0,否则返回错误编码
int pthread_mutex_unlock(pthread_mutex_t *mutex);

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则就会失败,而返回EBUSY。

3、编程实现加锁和解锁

代码如下:


#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int cnt=0;
pthread_mutex_t mutex;

void *func1(void *arg)
{
  printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t1:param is :%d\n",*((int *)arg));

  pthread_mutex_lock(&mutex);
  while(1)
  {
  printf("t1:cnt=%d\n",cnt++);
  sleep(1);
  if(cnt==3)
  {
    pthread_mutex_unlock(&mutex);
    //pthread_exit(NULL);
    printf("t1 quit =====================");
    exit(0);
  }
  }
}

void *func2(void *arg)
{
  printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t2:param is :%d\n",*((int *)arg));

  pthread_mutex_lock(&mutex);
  while(1)
  {
  printf("t2:cnt=%d\n",cnt);
  pthread_mutex_unlock(&mutex);
  sleep(1);
  }
}



int main()
{

  int ret;
  int param=100;
  pthread_t t1;
  pthread_t t2;

  pthread_mutex_init(&mutex,NULL);
  ret=pthread_create(&t1,NULL,func1,(void *)&param);

  if(ret==0)
 {
   printf("main create t1 success\n");
 }

  ret=pthread_create(&t2,NULL,func2,(void *)&param);

  if(ret==0)
 {
   printf("main create t2 success\n");
 }

 while(1)
 {
   printf("main:cnt=%d\n",cnt);
   sleep(1);
 }
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);

  pthread_mutex_destroy(&mutex);
  return 0;

}

运行结果:

图片[2]- Linux系统编程—线程- 如烟笔记

从运行结果,可以看到func1拿到锁之后,只会运行func1的内容,直到func1解锁,func2才能拿到锁,否则等待。

4、线程造成死锁情况

首先需要两把或两把以上的锁,线程1先拿到锁1,线程2先拿到锁2,然后线程1需要再拿到锁2才能运行,线程2需要再拿到锁1才能运行,所以两个线程都拿不到两把锁,而产生互相等待的现象。

代码如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int cnt=0;

pthread_mutex_t mutex;
pthread_mutex_t mutex2;

void *func1(void *arg)
{

  pthread_mutex_lock(&mutex);
  sleep(1);  //休眠
  pthread_mutex_lock(&mutex2);


  printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t1:param is :%d\n",*((int *)arg));

  pthread_mutex_unlock(&mutex);
  pthread_mutex_unlock(&mutex2);

}

void *func2(void *arg)
{

  pthread_mutex_lock(&mutex2);
  sleep(1);  //休眠
  pthread_mutex_lock(&mutex);

  printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t2:param is :%d\n",*((int *)arg));

  pthread_mutex_unlock(&mutex2);
  pthread_mutex_unlock(&mutex);
}

int main()
{

  int ret;
  int param=100;
  pthread_t t1;
  pthread_t t2;

  pthread_mutex_init(&mutex,NULL);

  pthread_mutex_init(&mutex2,NULL);

  ret=pthread_create(&t1,NULL,func1,(void *)&param);

  if(ret==0)
 {
   printf("main create t1 success\n");
 }

  ret=pthread_create(&t2,NULL,func2,(void *)&param);

  if(ret==0)
 {
   printf("main create t2 success\n");
 }
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);

  pthread_mutex_destroy(&mutex);
  pthread_mutex_destroy(&mutex2);

  return 0;

}

运行结果:

图片[3]- Linux系统编程—线程- 如烟笔记

由运行结果可知,两个线程产生互相等待的现象,造成死锁。

五、线程条件控制实现线程同步

  1. 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
  2. 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。

1、条件变量的创建及销毁

函数原型:


#include <pthread.h>

//动态初始化,创建成功返回0,否则返回错误编码
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//或静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//销毁
int pthread_cond_destroy(pthread_cond_t cond);

参数说明:

cond:指定为pthread_cond_t类型。创建成功会返回为条件变量的标识符。

attr:要用默认的属性初始化条件变量,只需把attr设置为NULL。

2、等待

函数原型:


#include <pthread.h>

//成功返回0,否则返回错误编码
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);

函数说明:        pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。        传递给pthread_cond_wait的互斥量对条件进行保护,对条件变量进行加锁。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁。这两个操作都是原子操作(是指一个操作中的所有动作要么全做,要么全不做)。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。
pthread_cond_wait调用成功返回时,会给该进程进行加锁。
  pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

3、触发

函数原型:


#include <pthread.h>

//成功返回0,否则返回-1
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);

函数说明:        这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。注意,一定要在改变条件状态以后再给线程发信号。

4、编程实现条件变量控制

代码如下:


#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int cnt=0;

//静态初始化
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void *func1(void *arg)
{

  printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t1:param is :%d\n",*((int *)arg));

  while(1)
  {
     pthread_cond_wait(&cond,&mutex);

     printf("t1 run ============================\n");
     printf("t1:cnt=%d\n",cnt);

     cnt=0;
     sleep(1);
  }
}

void *func2(void *arg)
{


  printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
  printf("t2:param is :%d\n",*((int *)arg));


  while(1)
  {

  printf("t2:%d\n",cnt);
  pthread_mutex_lock(&mutex);
  cnt++;
  pthread_mutex_unlock(&mutex);

if(cnt==3)
  {
     pthread_cond_signal(&cond);
  }
  sleep(1);
  }
}


int main()
{

  int ret;
  int param=100;
  pthread_t t1;
  pthread_t t2;
 //动态初始化
  //pthread_mutex_init(&mutex,NULL);  
  //pthread_cond_init(&cond,NULL);

  ret=pthread_create(&t1,NULL,func1,(void *)&param);

  if(ret==0)
 {
   printf("main create t1 success\n");
 }

  ret=pthread_create(&t2,NULL,func2,(void *)&param);

  if(ret==0)
 {
   printf("main create t2 success\n");
 }
  pthread_join(t1,NULL);
  pthread_join(t2,NULL);

  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);

  return 0;
}

运行结果:

图片[4]- Linux系统编程—线程- 如烟笔记

个人观点,仅供参考 搬运自公众号:New个程序员

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容