Linux c语言 多线程编程学习

概念

  • 在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。
  • 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;
  • 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

优点

  • 启动一个线程所花费的空间远远小于启动一个进程所花费的空间,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间
  • 同一进程下的线程之间共享数据空间,线程间方便的通信机制
  • 提高应用程序响应。
  • 操作系统会保证当线程 数不大于CPU数目时,不同的线程运行于不同的CPU上。
  • 一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

LinuxThreads实现机制

LinuxThreads是目前Linux平台上使用最为广泛的线程库。LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)参数来调用clone()创建”线程”,表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。它所实现的就是基于核心轻量级进程的”一对一”线程模型,在核外实现的线程又可以分为”一对一”、”多对一”两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,而后者则完全在核外实现多线程,调度也在用户态完成。线程之间的管理在核外函数库中实现。”一对一”模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。

编译需要注意:

在编译C的多线程时候,Linux系统下的多线程遵循POSIX线程接口,称为pthread。一方面必须指定Linux C语言线程库多线程库pthread,连接时需要使用库libpthread.a,才可以正确编译(例如:gcc test.c -o test -lpthread);另一方面要包含有关线程头文件#include <pthread.h>。 
Linux下使用eclipse时,Makefiel文件可以由eclipse自动生成,可以通过修改它的工程配置来改变Makefile的参数 
如:在使用线程操作时,需要添加-lpthread才能编译通过,在Link flags处添加编译选项:-lpthread。

多线程基本操作

1、线程的创建与等待

  • int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

    作用:用来创建一个线程 
    thread为指向线程标识符的指针,attr用来设置线程属性,start_routine是线程运行函数的起始地址,arg是运行函数的参数。 
    当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。

void function(void * t); //函数的传入参数为 void* 类型

pthread_t id; // 定义一个线程id

retv = pthread_create(&id, NULL, (void*)&function_name, (void*)t); //线程属性为默认值(后面分析),运行函数起始地址类型必须是 void* ,函数function传入参数 t 的类型也必须是 void *,传入多个数据要用结构体

if(retv != 0)

{ 线程创建失败! }

  • int pthread_join(pthread_t thread, void ** thread_return);

作用:用来等待一个线程的结束 
thread为被等待的线程标识符,thread_return为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。

/*有返回值*/

void * ret;

pthread_join(id,&ret);

/*无返回值*/

pthread_join(id,NULL);

  • void pthread_exit(void * retval);

函数结束了,调用它的线程也就结束了。唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。

void * function (void)

{

.....

pthread_exit((void*)retval);

}


pthread.c:

# include<stdio.h>

# include<unistd.h> // 包含sleep();

# include<stdlib.h>

# include<pthread.h>

# include<sched.h> //包含调度函数

 

void pthread1(void);

void* pthread2(void);

void pthread_3(void*);

 

int main(void)

{

pthread_t id1;

pthread_t id2;

pthread_t id3;

pthread_attr_t id1_attr; //定义属性变量

int retv = 0;

long int t = 55;

void * ret;

 

pthread_attr_init(&id1_attr);

pthread_attr_setscope(&id1_attr, PTHREAD_SCOPE_SYSTEM);

 

retv = pthread_create(&id1,&id1_attr,(void*)&pthread1,NULL); //pthread_create创建线程成功返回0,失败返回-1。

if(0 != retv)

{

printf("Create pthread error!\n");

return -1;

}

 

pthread_create(&id2,NULL,(void*)&pthread2,NULL);

 

pthread_join(id1,NULL);

pthread_join(id2,&ret); //等待线程id2结束,用ret接收返回值

 

pthread_create(&id3,NULL, (void*)&pthread_3,(void *)t); //创建线程id3,向线程函数传参数t

pthread_join(id3,NULL);

 

printf("Hello,i am the main process!\n");

printf("return the id2 is: %lu\n", (long int)ret);

 

return 0;

}

 

void pthread1(void)

{

printf("I am the first thread!\n");

return;

}

 

void* pthread2(void)

{

long int i = NULL;

 

while(i<10)

{

i++;

}

 

return (void*)i;

//pthread_exit((void*)i); //线程退出结束

}

 

void pthread_3(void * val)

{

printf("t的值为:%lu\n", (long int)val); //void*类型不能直接打印,8字节

return;

}


2、线程的属性

  • pthread_attr_init(&attr);

    作用:初始化线程属性

  • pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

作用:设置线程绑定状态 
attr是指向属性结构的指针,第二个参数是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。

示例:

pthread_attr_t attr;

/*初始化属性值,均设为默认值*/

pthread_attr_init(&attr);

pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

    作用:修改线程的分离状态属性
  • int pthread_attr_getdetazchstate(const pthread_attr_t * attr,int *detachstate);

    作用:获取线程的分离状态属性 
    第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)

  • int pthread_attr_setschedparam(pthread_attr_t *attr,struct sched_param *param);

    作用:设置线程的调度参数

  • int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);

    作用:得到线程的调度参数 
    线程的优先级,它存放在结构sched_param中,结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。(系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到) 
    注:如果不是编写实时程序,不建议修改线程的优先级。因为,调度策略是一件非常复杂的事情,如果不正确使用会导致程序错误,从而导致死锁等问题。如:在多线程应用程序中为线程设置不同的优先级别,有可能因为共享资源而导致优先级倒置。

  • int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);

    作用:得到线程的调度策略

  • int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);

    作用:设置线程的调度策略 
    policy为调度策略或指向调度策略的指针。调度策略可能的值是先进先出(SCHED_FIFO)、轮转法(SCHED_RR),或其它(SCHED_OTHER)。 
    SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。 
    SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR 
    策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时,运行的线程将被抢占以便准备好的线程可以执行。 
    当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等待或等待加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级 的SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥量,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。

  • int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);

    作用:获得线程的继承性

  • int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

    作用:设置线程的继承性 
    继承性决定调度的参数是从创建的进程中继承还是使用在 schedpolicy和schedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程 的调度策略和参数,必须先设置该属性。继承性的可能值是PTHREAD_INHERIT_SCHED,表示新现成将继承创建线程的调度策略和参数;或者PTHREAD_EXPLICIT_SCHED(缺省),表示使用在schedpolicy和schedparam属性中显式设置的调度策略和参数。


pthread2.c:

# include<stdio.h>

# include<pthread.h>

 

void function(void * data)

{

int i;

for(i=0; i<=2; i++)

if(i == 2)

printf("I'm thread th%ld!\n", (long int)data);

}

 

int main(void)

{

pthread_t th1;

pthread_t th2;

 

/*线程属性 */

pthread_attr_t attr_th1;

pthread_attr_t attr_th2;

 

long int a=1, b=2;

 

printf("I'm the main prosser!\n");

 

pthread_attr_setscope(&attr_th1, PTHREAD_SCOPE_PROCESS); //非绑定

pthread_attr_setscope(&attr_th2, PTHREAD_SCOPE_SYSTEM); //绑定

 

/*线程属性初始化*/

pthread_attr_init(&attr_th1);

pthread_attr_init(&attr_th2);

 

/*创建线程*/

pthread_create(&th1, &attr_th1, (void*)&function, (void *)a);

pthread_create(&th2, &attr_th2, (void*)&function, (void *)b);

 

/*等待线程结束*/

pthread_join(th1, NULL);

pthread_join(th2, NULL);

 

return 0;

}


3、互斥锁

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

    作用:用来生成一个互斥锁 
    pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //生成静态锁 
    pthread_mutex_init(); 一般用来生成动态锁

  • pthread_mutexattr_init(pthread_mutexattr_t * mattr);

    作用:声明特定属性的互斥锁,须调用函数

  • pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);

    作用:设置属性pshared

  • pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared);

    作用:获取锁的范围 
    它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。默认是后者,表示进程内使用锁

  • pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);

    作用:设置锁的类型

  • pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type);

    作用:获取锁的类型 
    PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 
    PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。 
    PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。 
    PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

  • pthread_mutex_lock();

    作用:声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止

  • pthread_mutex_trylock();

    作用:它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。

  • int pthread_mutex_unlock(pthread_mutex_t *mutex);

    作用:解锁

总结:

1) 只能用于”锁”住临界代码区域 
2) 一个线程加的锁必须由该线程解锁. 
锁几乎是我们学习同步时最开始接触到的一个策略,也是最简单, 最直白的策略.

4、条件变量

  • int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

    作用:初始化一个条件变量 
    结构pthread_condattr_t是条件变量的属性结构,用它来设置条件变量是进程内可用还是进程间可用, 默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用;PTHREAD_PROCESS_SHARED则为多个进程间各线程公用。

  • int pthread_cond_destroy(pthread_cond_t *cond);

    作用:注销条件变量

  • int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);

    作用:条件等待 ,mutex用于保护条件 
    注:受signal激活后,wait先解锁mutex,pthread_cond_wait执行最后一个操作:重新锁定mutex;苏醒之前不会消耗CPU周期

  • int pthread_cond_signal(pthread_cond_t * cond);

    作用:条件激活 
    注:在条件满足时,给正在等待的线程发送信息,唤醒该线程,wait的线程继续执行。


pthread3.c:

#include<stdio.h>

# include<pthread.h>

#include<unistd.h>

# include<malloc.h>

 

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 

struct node {

int n_number;

struct node *n_next;

 

} *head = NULL;

 

static void *thread_func(void)

{

struct node *p = NULL;

 

while (1)

{

pthread_mutex_lock(&mtx);

while (head == NULL)

{

pthread_cond_wait(&cond, &mtx);

}

p = head;

head = head->n_next;

printf("Got %d from front of queue\n", p->n_number);

free(p);

p = NULL;

pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁

}

 

return 0;

 

}

 

int main(void)

{

pthread_t tid;

int i;

struct node *p;

pthread_create(&tid, NULL, (void*)&thread_func, NULL);

for (i = 0; i < 10; i++)

{

p = (struct node *)malloc(sizeof(struct node));

p->n_number = i;

pthread_mutex_lock(&mtx);

p->n_next = head;

head = p;

pthread_cond_signal(&cond);

pthread_mutex_unlock(&mtx); //解锁

sleep(1);

 

}

 

printf("head'data is %d\n", head->n_number);

printf("thread 1 wanna end the line.So cancel thread 2.\n");

pthread_cancel(tid);

pthread_join(tid, NULL);

printf("p'data is %d.\n", p->n_number);

printf("All done -- exiting\n");

return 0;

}


5、信号量

信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中。同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁。但是,信号的效率不如互斥锁高。缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的。使用头文件#include <semaphore.h>

  • int sem_init (sem_t * sem, int pshared, unsigned int value);

    作用:对由sem指定的信号量进行初始化。 
    pshared为0时,表示这个信号量只是当前进程中的信号量(该进程内的 所有线程使用),不为0,这个信号量可能可以在两个进程中共享;value给出了信号量的初始值 
    注: 
    (1)多个线程决不能初始化同一个信号。 
    (2)不得对其他线程正在使用的信号重新初始化。

  • int sem_wait(sem_t *sem);

    作用:用于接受信号 
    当sem>0时就能接受到信号,然后将sem– ;sem为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。

  • int sem_post(sem_t *sem);

    作用:给信号量的值加1 。注:如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。

  • int sem_destroy(sem_t *sem);

    作用:销毁与sem所指示的未命名信号相关联的任何状态。释放内存空间


pthread4.c:

#include<stdio.h>

#include <string.h>

#include <pthread.h>

#include <errno.h>

#include <semaphore.h>

#define BUFSIZE 4

#define NUMBER 8

 

int sum_of_number=0;

 

/* 可读 和 可写资源数*/

sem_t write_res_number;

sem_t read_res_number;

 

/* 循环队列 */

struct recycle_buffer

{

int buffer[BUFSIZE];

int head,tail;

}re_buf;

 

/* 用于实现临界区的互斥锁,我们对其初始化*/

pthread_mutex_t buffer_mutex=PTHREAD_MUTEX_INITIALIZER;

 

static void producer(void)

{

//int * num_of_w = NULL;

int i;

for(i=0;i<=NUMBER;i++)

{

/* 减少可写的资源数 */

sem_wait(&write_res_number);

/* 进入互斥区 */

pthread_mutex_lock(&buffer_mutex);

/*将数据复制到缓冲区的尾部*/

re_buf.buffer[re_buf.tail]=i;

re_buf.tail=(re_buf.tail+1)%BUFSIZE;

printf("procuder %lu write %d.\n", pthread_self(),i); //pthread_self();获得线程自身的ID。pthread_t的类型为unsigned long int

/*离开互斥区*/

pthread_mutex_unlock(&buffer_mutex);

/*增加可读资源数*/

sem_post(&read_res_number);

 

//sem_getvalue(&write_res_number, num_of_w);

//printf("%d\n", *num_of_w);

}

/* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/

return;

 

}

 

static void consumer(void)

{

int i,num;

for(i=0;i<=NUMBER;i++)

{

/* 减少可读资源数 */

sem_wait(&read_res_number);

/* 进入互斥区*/

pthread_mutex_lock(&buffer_mutex);

/* 从缓冲区的头部获取数据*/

num = re_buf.buffer[re_buf.head];

re_buf.head = (re_buf.head+1)%BUFSIZE;

printf("consumer %lu read %d.\n", pthread_self(),num);

/* 离开互斥区*/

pthread_mutex_unlock(&buffer_mutex);

sum_of_number+=num;

/* 增加可写资源数*/

sem_post(&write_res_number);

}

/* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/

return;

}

 

int main(void)

{

/* 用于保存线程的线程号 */

pthread_t p_tid;

pthread_t c_tid;

int i;

re_buf.head=0;

re_buf.tail=0;

for(i=0;i<BUFSIZE;i++)

re_buf.buffer[i] =0;

/* 初始化可写资源数为循环队列的单元数 */

sem_init(&write_res_number,0,BUFSIZE); // 这里限定了可写的bufsize,当写线程写满buf时,会阻塞,等待读线程读取

/* 初始化可读资源数为0 */

sem_init(&read_res_number,0,0);

/* 创建两个线程,线程函数分别是 producer 和 consumer */

/* 这两个线程将使用系统的缺省的线程设置,如线程的堆栈大小、线程调度策略和相应的优先级等等*/

pthread_create(&p_tid,NULL,(void*)&producer,NULL);

pthread_create(&c_tid,NULL,(void*)&consumer,NULL);

/*等待两个线程完成退出*/

pthread_join(p_tid,NULL);

pthread_join(c_tid,NULL);

 

printf("the head is : %d\n", re_buf.head);

printf("the tail is : %d\n", re_buf.tail);

printf("The sum of number is %d\n",sum_of_number);

 

return 0;

}


下面是一个死锁的例子:

# include<stdio.h>

# include<pthread.h>

# include<unistd.h>

 

static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 

void *function1(void *val)

{

int i = 1;

while(i<3)

{

pthread_cond_wait(&cond, &mutex1);

 

pthread_mutex_lock(&mutex1);

i++;

printf("%lu\n", (long int)val);

pthread_mutex_unlock(&mutex1);

}

pthread_exit(NULL);

}

 

void *function2(void *val)

{

int i = 0;

 

while(i<3)

{

pthread_mutex_lock(&mutex1);

 

printf("%lu\n", (long int)val);

pthread_cond_signal(&cond);

 

i++;

pthread_mutex_unlock(&mutex1);

sleep(1);

}

 

pthread_exit(NULL);

}

 

int main(void)

{

pthread_t id1;

pthread_t id2;

 

long int t1 = 55;

long int t2 = 66;

 

pthread_create(&id1, NULL, function1, (void*)t1);

pthread_create(&id2, NULL, function2, (void*)t2);

 

pthread_join(id1, NULL);

 

return 0;

}

 

 

转载https://www.cnblogs.com/bianchengshizhe/p/5130705.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值