目录
一:线程的创建与等待
线程创建函数:
Create a new thread, starting with execution of START-ROUTINE
getting passed ARG. Creation attributed come from ATTR. The new
handle is stored in *NEWTHREAD.
extern int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg) ;
第一个参数:指向线程标示符的指针
第二个参数:设置线程的属性,若采用该默认属性,设为NULL
第三个参数:线程运行函数的起始地址
第四个参数:传递给线程函数的参数,若不需要传递参数,设为NULL
注意:当创建线程成功时,函数返回0,若不为0则表明线程创建失败
线程等待函数:
Make calling thread wait for termination of the thread TH. The
exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN
is not NULL.
This function is a cancellation point and therefore not marked with
__THROW.
extern int pthread_join (pthread_t __th, void **__thread_return);
第一个参数:被等待的线程标识符
第二个参数:用户定义的指针,它可以用来存储被等待线程的返回值
作用:该函数为线程阻塞函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被回收。
线程退出函数:
extern void pthread_exit (void *__retval)
唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。
注意:一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH
下面来看两个例子
例一:
#include <stdio.h>
#include <pthread.h>
#define NUM 6
int main()
{
void *print_message(void*);
pthread_t t1,t2;
pthread_create(&t1,NULL,print_message,(void*)"hello,");
pthread_create(&t2,NULL,print_message,(void*)"world!\n");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
void *print_message(void *m)
{
char *cp=(char *)m;
int i;
for(i=0;i<NUM;i++)
{
printf("%s",cp);
fflush(stdout);
sleep(1);
}
}
运行结果
[root@ftz c_test]# gcc pthread_hello.c -o pthread_hello -lpthread
[root@ftz c_test]# ./pthread_hello
hello,world!
hello,world!
hello,world!
hello,world!
hello,world!
hello,world!
可以看到hello,world同时打印出来,说明创建的两个线程都在运行。如果把sleep去掉或者把时间调短会是什么样的打印结果呢?多运行几次打印的结果是固定的吗?读者可以自行调试一下,这样会更加加深对线程的理解。
例二:
#include <stdio.h>
#include <unistd.h> //包含sleep()
#include <pthread.h>
#include <sched.h> //包含调度函数
void pthread1(void)
{
printf("I'm the first thread!\n");
return;
}
void *pthread2()
{
long int i=0;
while(i<10)
{
i++;
printf("I'm thread2 and the i is : %lu\n",i);
}
return (void*)i;
pthread_exit((void*)i);
}
void pthread3(void *val)
{
printf("t的值为:%lu\n",(long int)val);
return;
}
int main()
{
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_attr_t id1_attr; //定义属性变量
int rev = 0;
long int t = 55;
void *ret;
pthread_attr_init(&id1_attr);
pthread_attr_setscope(&id1_attr,PTHREAD_SCOPE_SYSTEM);
rev = pthread_create(&id1,&id1_attr,(void*)&pthread1,NULL);
if(rev != 0)
{
printf("Create pthread1 error!\n");
return -1;
}
pthread_create(&id2,NULL,pthread2,NULL);
pthread_join(id1,NULL);
pthread_join(id2,&ret);
pthread_create(&id3,NULL,(void *)&pthread3,(void *)t); //创建线程3并向线程函数传参数t
pthread_join(id3,NULL);
printf("Hello,I'm the mian process!\n");
printf("return the id2 is : %lu\n",(long int)ret);
return 0;
}
运行结果:
[root@ftz c_test]# gcc pthread_num.c -o pthread_num -lpthread
[root@ftz c_test]# ./pthread_num
I'm the first thread!
I'm thread2 and the i is : 1
I'm thread2 and the i is : 2
I'm thread2 and the i is : 3
I'm thread2 and the i is : 4
I'm thread2 and the i is : 5
I'm thread2 and the i is : 6
I'm thread2 and the i is : 7
I'm thread2 and the i is : 8
I'm thread2 and the i is : 9
I'm thread2 and the i is : 10
t的值为:55
Hello,I'm the mian process!
return the id2 is : 10
从运行的结果可以看到,在第二个线程函数内对i进行++操作,并打印出来,最后在函数中返回i,在主函数的等待函数pthread_join(id2,&ret);中,i将会传递给ret,最后ret打印出来也为10.创建的第三个线程函数pthread_create(&id3,NULL,(void *)&pthread3,(void *)t);将t传递给线程3函数pthread3,并且打印出该参数,最后打印的结果也的确是t的值55。在主函数中,三个等待函数pthread_join一一返回后,最后运行到主函数的打印printf("Hello,I'm the mian process!\n");
二:互斥锁
在多线程编程中,对于临界资源我们都要进行互斥保护,比如多个文件需要往一个文件里写,如果不加控制结果是不可控的。必须保证一个线程在写的时候,其他的线程不能拿到这个资源
互斥锁用来保证一段时间内只有一个线程在执行一段代码。假设有一个卖票系统,利用多线程来提高系统的响应时间,但是如果不加限制,可能同一张票被多个线程获取,这就会导致灾难性的问题。 有兴趣的可以了解一下操作系统著名的银行家吃饭算法。
互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢占同一块资源
extern int pthread_mutex_init (pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr)
作用:用来生成一个互斥锁,该函数一般用来生成动态锁
extern int pthread_mutex_lock (pthread_mutex_t *__mutex)
作用:声明开始用互斥上锁,此后的代码直至调用pthread_mutex_unlock为止
extern int pthread_mutex_unlock (pthread_mutex_t *__mutex)
作用:解锁
extern int pthread_mutex_trylock (pthread_mutex_t *__mutex)
作用:它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,此时可以根据灵活的针对死锁做出相应的处理。
来看下面一个互斥锁的例子
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#define NUM 10
int number = 0;
pthread_t thread[2]; //建立一个线程数组
pthread_mutex_t mut;
void *thread1()
{
int i;
printf("thread1 : I'm thread1 \n");
for (i=0;i<NUM;i++)
{
pthread_mutex_lock(&mut);
number++;
printf("The thread1 of number is : %d\n",number);
pthread_mutex_unlock(&mut);
sleep(1);
}
printf("thread1 : 主函数在等我完成任务么? \n");
pthread_exit(NULL);
}
void *thread2()
{
int i;
printf("thread2 : I'm thread2 \n");
for (i=0;i<NUM;i++)
{
pthread_mutex_lock(&mut);
number++;
printf("The thread2 of number is : %d\n",number);
pthread_mutex_unlock(&mut);
sleep(1);
}
printf("thread2 : 主函数在等我完成任务么? \n");
pthread_exit(NULL);
}
void thread_create(void)
{
int temp;
memset(&thread, 0, sizeof(thread));
if( (temp = pthread_create(&thread[0], NULL, thread1, NULL)) == 0) //返回值为0即创建成功
printf("线程1创建成功\n");
else
printf("线程1创建失败\n");
if( (temp = pthread_create(&thread[1], NULL, thread2, NULL)) == 0) //返回值为0即创建成功
printf("线程2创建成功\n");
else
printf("线程2创建失败\n");
}
void thread_wait(void)
{
if(thread[0] != 0)
{
pthread_join(thread[0],NULL);
printf("线程1已经结束\n");
}
if(thread[1] != 0)
{
pthread_join(thread[1],NULL);
printf("线程2已经结束\n");
}
}
int main()
{
pthread_mutex_init(&mut,NULL);
printf("主函数马上就要创建线程了,马上哈\n");
thread_create();
printf("主函数正在等待线程完成任务,别急哈\n");
thread_wait();
return 0;
}
这个代码要做的事也非常简单,创建两个线程,分别对一个全局变量做++操作,每个都++十次。线看看以上正确代码,加了互斥后的运行结果
[root@ftz c_test]# gcc pthread_mutex_test.c -o pthread_mutex_test -lpthread
[root@ftz c_test]# ./pthread_mutex_test
主函数马上就要创建线程了,马上哈
线程1创建成功
线程2创建成功
主函数正在等待线程完成任务,别急哈
thread2 : I'm thread2
thread1 : I'm thread1
The thread2 of number is : 1
The thread1 of number is : 2
The thread2 of number is : 3
The thread1 of number is : 4
The thread2 of number is : 5
The thread1 of number is : 6
The thread2 of number is : 7
The thread1 of number is : 8
The thread2 of number is : 9
The thread1 of number is : 10
The thread2 of number is : 11
The thread1 of number is : 12
The thread2 of number is : 13
The thread1 of number is : 14
The thread2 of number is : 15
The thread1 of number is : 16
The thread2 of number is : 17
The thread1 of number is : 18
The thread2 of number is : 19
The thread1 of number is : 20
thread2 : 主函数在等我完成任务么?
thread1 : 主函数在等我完成任务么?
线程1已经结束
线程2已经结束
从结果中可以看到number在顺序的加1,并没有出现重复的情况。pthread_mutex_lock声明开始用互斥上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待另一个线程释放此互斥锁。上面这个例子就是说同一时间只有一个线程对number进行加一操作,防止两个线程同时对number进行操作。
但是假如没有互斥这一步呢,我们注释掉pthread_mutex_lock(&mut);和pthread_mutex_unlock(&mut);再运行程序看看会发生什么,读者可以自行去调试
三:自旋锁
自旋锁是为实现保护共享资源而提出一种锁机制。其实自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用,无论是互斥锁还是自旋锁,在任何时候最多只能有一个保持者,也就是说在任何时刻最多只能有一个执行单元获得锁,但是两者在调度机制上不同,对于互斥锁如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别人的执行单元保持,调用者就一直循环在那里看是否该自旋锁保持者已经释放了锁
/* Initialize the spinlock LOCK. If PSHARED is nonzero the spinlock can
be shared between different processes. */
extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared)
作用:用来申请使用自旋锁所需要的资源并且将它初始化为非锁定状态。pshared的取值及其含义:
- PTHREAD_PROCESS_SHARED:该自旋锁可以在多个进程中的线程之间共享。
- PTHREAD_PROCESS_PRIVATE:仅初始化本自旋锁的线程所在的进程内的线程才能够使用该自旋锁。
/* Wait until spinlock LOCK is retrieved. */
extern int pthread_spin_lock (pthread_spinlock_t *__lock)
作用:用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的
/* Try to lock spinlock LOCK. */
extern int pthread_spin_trylock (pthread_spinlock_t *__lock)
作用:尝试获取指定的自旋锁,如果无法获取则理解返回失败
/* Release spinlock LOCK. */
extern int pthread_spin_unlock (pthread_spinlock_t *__lock)
作用:用于释放指定的自旋锁
/* Destroy the spinlock LOCK. */
extern int pthread_spin_destroy (pthread_spinlock_t *__lock)
作用:用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)在调用该函数之后如果没有调用 pthread_spin_init重新初始化自旋锁,则任何尝试使用该锁的调用的结果都是未定义的。如果调用该函数时自旋锁正在被使用或者自旋锁未被初 始化则结果是未定义的。
自旋锁的用法和互斥锁基本差不多,对于自旋锁来说,它只需要消耗很少的资源来建立锁;随后当线程被阻塞时,它就会一直重复检查看锁是否可用了,也就是说当自旋锁处于等待状态时它会一直消耗CPU时间。
对于互斥锁来说,与自旋锁相比它需要消耗大量的系统资源来建立锁;随后当线程被阻塞时,线程的调度状态被修改,并且线程被加入等待线程队列;最后当锁可用 时,在获取锁之前,线程会被从等待队列取出并更改其调度状态;但是在线程被阻塞期间,它不消耗CPU资源。
因此自旋锁和互斥锁适用于不同的场景。自旋锁适用于那些仅需要阻塞很短时间的场景,而互斥锁适用于那些可能会阻塞很长时间的场景。
此处可以参考:posix多线程有感--自旋锁-taohorse-ChinaUnix博客
直接来看一个自旋锁和互斥锁比较的例子
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/timeb.h>
#include <stdlib.h>
static int num = 0;
static int count = 10000000;
static pthread_spinlock_t spin;
//static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
long long getSystemTime()
{
struct timeb t;
ftime(&t);
return 1000 * t.time + t.millitm;
}
void *fun2()
{
pthread_t thread_id = pthread_self();
printf("the thread2 id is %ld\n",(long)thread_id);
int i=1;
for (;i<=count;i++)
{
pthread_spin_lock(&spin);
//pthread_mutex_lock(&mutex);
num += 1;
pthread_spin_unlock(&spin);
//pthread_mutex_unlock(&mutex);
}
}
int main()
{
int err;
pthread_t thread1;
pthread_t thread2;
pthread_spin_init(&spin,PTHREAD_PROCESS_PRIVATE);
thread1 = pthread_self();
printf("the thread1 id is %ld\n",(long)thread1);
long long start = getSystemTime();
err = pthread_create(&thread2,NULL,fun2,NULL);
if(err != 0)
{
Perror("can't create thread2\n");
}
int i=1;
for(;i<=count;i++)
{
pthread_spin_lock(&spin);
//pthread_mutex_lock(&mutex);
num += 1;
pthread_spin_unlock(&spin);
//pthread_mutex_unlock(&mutex);
}
pthread_join(thread2 ,NULL);
long long end = getSystemTime();
printf("The num is %d, pay %lld ms\n",num,(end-start));
return 0;
}
将一个变量count利用自旋锁和互斥锁逐一的加到20000000,比较两者执行的速度,具体运行的时间读者可以自行在机器上运行。
四:条件变量
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正在被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新检测条件是否满足。
/* Initialize condition variable COND using attributes ATTR, or use
the default values if later is NULL. */
extern int pthread_cond_init (pthread_cond_t *__restrict __cond,
const pthread_condattr_t *__restrict __cond_attr)
作用:初始化条件变量函数
/* Destroy condition variable COND. */
extern int pthread_cond_destroy (pthread_cond_t *__cond)
作用:释放条件变量函数
/* Wake up one thread waiting for condition variable COND. */
extern int pthread_cond_signal (pthread_cond_t *__cond)
作用: 唤醒一个阻塞在条件变量cond上的线程。
/* Wake up all threads waiting for condition variables COND. */
extern int pthread_cond_broadcast (pthread_cond_t *__cond)
作用:用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,得小心使用这个函数
/* Wait for condition variable COND to be signaled or broadcast.
MUTEX is assumed to be locked before.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex)
作用:使线程阻塞在一个条件变量上
来看一个例子
#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 *next;
}*head=NULL;
static void *thread_func()
{
struct node *p=NULL;
while(1)
{
pthread_mutex_lock(&mtx);
//printf("the head is %d\n",head);
while(head == NULL)
{
pthread_cond_wait(&cond,&mtx);
}
p=head;
head=head->next;
printf("Got %d from front of queue\n",p->n_number);
free(p);
p=NULL;
pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
}
return 0;
}
int main()
{
pthread_t tid;
int i;
struct node *p;
pthread_cond_init(&cond,NULL);
pthread_create(&tid,NULL,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->next=head;
head = p;
if(p->n_number % 9 == 0)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);
printf("the i is %d\n",i);
//printf("the head is %d\n",head);
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("All done------------exiting\n");
return 0;
}
运行结果
[root@ftz c_test]# ./pthread_cond
the i is 0
Got 0 from front of queue
the i is 1
the i is 2
the i is 3
the i is 4
the i is 5
the i is 6
the i is 7
the i is 8
the i is 9
Got 9 from front of queue
Got 8 from front of queue
Got 7 from front of queue
Got 6 from front of queue
Got 5 from front of queue
Got 4 from front of queue
Got 3 from front of queue
Got 2 from front of queue
Got 1 from front of queue
thread 1 wanna end the line.So cancel thread 2.
All done------------exiting
从运行结果中可以看到,当最开始head=null时,线程thread_func里的判断条件满足,线程处于阻塞状态,在主程序里,运行到if(p->n_number % 9 == 0),判断条件也满足,此时运行
pthread_cond_signal(&cond);唤醒处于阻塞状态的线程thread_func,有一个打印Got 0 from front of queue。此时head再次等于NULL,满足阻塞条件,但是在主线程里要一直等到p->n_number=9才能再次唤醒线程