今天是最后一篇关于Linux线程编程的文章分享,在这里我们先掌握基础的概念及其应用,后面在慢慢去深入学习。最近看到一句说的非常在理:理论’是你知道是这样,但它却不好用。‘实践’是它很好用,但你不知道是为什么。我想大多数学习者,和我一样,在学习的过程中,都会或多或少的有这种情况,不过自己坚信,你把基础打好(同时学的过程中,不要好高骛远,三心二意的,把自己先暂时用到的东西学明白,再去学其他东西,不要当前的,没学会,又跑去学其他的,而且又学不会,这样浪费时间和精力;这个这里基础打好,举个例子,你的c语言功底要打好,对指针的使用非常熟悉,甚至一些高级用法就是要平时慢慢积累和总结,以及内存原理要知道为什么是这样等方面),后面实战的话,就好多了,至少不会说我这个东西不会那个东西又不会,这样会让自己很痛苦当初为啥没学好基础,现在实战中漏洞百出。好了,废话不多说了,开始下面的主题分享:
一、互斥锁:
1、什么是互斥锁?
这里的话,我举一个日常生活的例子来引入这个概念(不是很好,不要见外,主要是为了好理解,哈哈。)。当你去厕所上厕所的时候,你的先打开厕所的门来,不然进不去;进去之后,你又得把门关上,当你上厕所上到一半的时候,突然外面有人也要来上厕所了,但是你把门给锁住了,所以他就知道里面有人在上厕所,这个人就只能在外面等你上完厕所出来,他才能进去上厕所。
我们要讲的互斥锁和上面举得不是很好的例子,不过道理是一样的:当多线程中的一个线程正在访问一个共享变量时,它会先上锁(也就是说上锁之后,其他线程不能对这个共享变量操作了,其他线程处于等待状态),然后对这个共享变量操作使用完之后,它才会把这个锁给打开,接着给其他线程来使用这个共享变量,其它线程在操作这个共享变量的时候,也是按照这个规律来操作的,这样的话,就能实现多线程的同步了(这里的同步,是多线程对共享的变量达到相同的操作)。
2、互斥锁操作函数介绍:
——注意在使用man手册查看这些互斥锁函数的时候,你会发现找不到,这里你得先安装它的包,使用这个命令来安装:sudo apt-get install manpages-posix-dev,如下图所示:
a、互斥锁初始化函数:
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
参数说明:
上面的第一个参数就是我们要操作的共享变量,第二个参数一般默认使用NULL。
b、解锁和上锁函数:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
DESCRIPTION
The mutex object referenced by mutex shall be locked by a call to pthread_mutex_lock() that returns zero or [EOWNERDEAD]. If the mutex is already locked by another
thread, the calling thread shall block until the mutex becomes available. This operation shall return with the mutex object referenced by mutex in the locked state with
the calling thread as its owner. If a thread attempts to relock a mutex that it has already locked, pthread_mutex_lock() shall behave as described in the Relock column of
the following table. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, pthread_mutex_unlock() shall behave as described in the
Unlock When Not Owner column of the following table.
函数返回值:
RETURN VALUE
If successful, the pthread_mutex_lock(), pthread_mutex_trylock(), and pthread_mutex_unlock() functions shall return zero; otherwise, an error number shall be returned to
indicate the error.
ERRORS
The pthread_mutex_lock() and pthread_mutex_trylock() functions shall fail if:
EAGAIN The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded.
EINVAL The mutex was created with the protocol attribute having the value PTHREAD_PRIO_PROTECT and the calling thread's priority is higher than the mutex's current prior‐
ity ceiling.
ENOTRECOVERABLE
The state protected by the mutex is not recoverable.
EOWNERDEAD
The mutex is a robust mutex and the process containing the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired by the
calling thread and it is up to the new owner to make the state consistent.
The pthread_mutex_lock() function shall fail if:
EDEADLK
The mutex type is PTHREAD_MUTEX_ERRORCHECK and the current thread already owns the mutex.
The pthread_mutex_trylock() function shall fail if:
EBUSY The mutex could not be acquired because it was already locked.
The pthread_mutex_unlock() function shall fail if:
EPERM The mutex type is PTHREAD_MUTEX_ERRORCHECK or PTHREAD_MUTEX_RECURSIVE, or the mutex is a robust mutex, and the current thread does not own the mutex.
The pthread_mutex_lock() and pthread_mutex_trylock() functions may fail if:
EOWNERDEAD
The mutex is a robust mutex and the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired by the calling thread and it is
up to the new owner to make the state consistent.
The pthread_mutex_lock() function may fail if:
EOWNERDEAD
The mutex is a robust mutex and the previous owning thread terminated while holding the mutex lock. The mutex lock shall be acquired by the calling thread and it is
up to the new owner to make the state consistent.
The pthread_mutex_lock() function may fail if:
EDEADLK
A deadlock condition was detected.
These functions shall not return an error code of [EINTR].
The following sections are informative.
参数说明:
这里面的参数和上面使用一样。不过要注意函数pthread_mutex_trylock会尝试对互斥量加锁,如果该互斥量已经被锁住,函数调用失败,返回EBUSY,否则加锁成功返回0,线程不会被阻塞;而函数pthread_mutex_lock返回成功时,不会这样,会用阻塞。
c、锁资源释放函数:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数说明:
这里参数的话,和上面使用一样,但是要注意这个函数的使用,它是可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
3、代码实战演示:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
char buf[200] = {0};
pthread_mutex_t mutex;
unsigned int flag = 0;
// 子线程程序,作用是统计buf中的字符个数并打印
void *func(void *arg)
{
// 子线程首先应该有个循环
// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
// 长度,然后打印;完成后再次被阻塞
//while (strncmp(buf, "end", 3) != 0)
sleep(1);
while (flag == 0)
{
pthread_mutex_lock(&mutex);
printf("本次输入了%d个字符\n", strlen(buf));
memset(buf, 0, sizeof(buf));
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = -1;
pthread_t th = -1;
pthread_mutex_init(&mutex, NULL);
ret = pthread_create(&th, NULL, func, NULL);
if (ret != 0)
{
printf("pthread_create error.\n");
exit(-1);
}
printf("输入一个字符串,以回车结束\n");
while (1)
{
pthread_mutex_lock(&mutex);
scanf("%s", buf);
pthread_mutex_unlock(&mutex);
// 去比较用户输入的是不是end,如果是则退出,如果不是则继续
if (!strncmp(buf, "end", 3))
{
printf("程序结束\n");
flag = 1;
//exit(0);
break;
}
sleep(1);
// 主线程在收到用户收入的字符串,并且确认不是end后
// 就去发信号激活子线程来计数。
// 子线程被阻塞,主线程可以激活,这就是线程的同步问题。
// 信号量就可以用来实现这个线程同步
}
// 回收子线程
printf("等待回收子线程\n");
ret = pthread_join(th, NULL);
if (ret != 0)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("子线程回收成功\n");
pthread_mutex_destroy(&mutex);
return 0;
}
演示结果:
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
输入一个字符串,以回车结束
jkdjk
本次输入了5个字符
jkdjkdj
本次输入了7个字符
end
程序结束
等待回收子线程
本次输入了3个字符
子线程回收成功
说明:
上面的演示是使用了上一篇的代码演示,上一篇文章里面我们使用了信号量来实现多线程同步操作,这里是使用互斥锁来实现多线程。
二、条件变量:
1、什么是条件变量:
我们还是以日常生活的中例子来引出概念来:在我们去医院看病的时候,你的先挂号才能看病,而且人非常多,一个医生根本忙不过来(大量任务需要处理),所以每个看病的人挂号时间非常短暂的就能解决掉(每个任务的处理时间很短),所以我们一般去医院挂号,会有许多窗口的,但是还是有时候要排队的,然后类似下面这种场景,大家肯定都遇到过的:
通过上面的例子,我们可以看出,条件变量与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止(比如挂号的人都弄完了,它就会自动停止挂号)。但是通常条件变量和互斥锁同时使用(如上面的例子,各个窗口挂号互不干扰)。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。总之条件变量要和互斥锁一起来用使用。
2、条件变量函数介绍:
a、初始化函数
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
说明:
函数pthread_cond_init里面的第一个参数就是我们要使用的那个条件变量来控制线程间的同步,第一个参数一般默为NULL;函数pthread_cond_destroy里面的参数也是一个条件变量。
原注释:
The pthread_cond_destroy() function shall destroy the given condition variable specified by cond; the object becomes, in effect, uninitialized. An implementation may cause
pthread_cond_destroy() to set the object referenced by cond to an invalid value. A destroyed condition variable object can be reinitialized using pthread_cond_init(); the
results of otherwise referencing the object after it has been destroyed are undefined.
It shall be safe to destroy an initialized condition variable upon which no threads are currently blocked. Attempting to destroy a condition variable upon which other
threads are currently blocked results in undefined behavior.
The pthread_cond_init() function shall initialize the condition variable referenced by cond with attributes referenced by attr. If attr is NULL, the default condition
variable attributes shall be used; the effect is the same as passing the address of a default condition variable attributes object. Upon successful initialization, the
state of the condition variable shall become initialized.
Only cond itself may be used for performing synchronization. The result of referring to copies of cond in calls to pthread_cond_wait(), pthread_cond_timedwait(),
pthread_cond_signal(), pthread_cond_broadcast(), and pthread_cond_destroy() is undefined.
b、发送信号函数:
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
描述:
DESCRIPTION
These functions shall unblock threads blocked on a condition variable.
The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond.
The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on
cond).
If more than one thread is blocked on a condition variable, the scheduling policy shall determine the order in which threads are unblocked. When each thread unblocked as a
result of a pthread_cond_broadcast() or pthread_cond_signal() returns from its call to pthread_cond_wait() or pthread_cond_timedwait(), the thread shall own the mutex with
which it called pthread_cond_wait() or pthread_cond_timedwait(). The thread(s) that are unblocked shall contend for the mutex according to the scheduling policy (if
applicable), and as if each had called pthread_mutex_lock().
The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that threads calling
pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behavior is required,
then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().
The pthread_cond_broadcast() and pthread_cond_signal() functions shall have no effect if there are no threads currently blocked on cond.
The behavior is undefined if the value specified by the cond argument to pthread_cond_broadcast() or pthread_cond_signal() does not refer to an initialized condition vari‐
able.
返回值:
RETURN VALUE
If successful, the pthread_cond_broadcast() and pthread_cond_signal() functions shall return zero; otherwise, an error number shall be returned to indicate the error.
ERRORS
These functions shall not return an error code of
[EINTR].
The following sections are informative.
说明:
上面的pthread_cond_signal 函数和 pthread_cond_broadcast函数 的区别在于:
c、等待函数:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
说明:
第一个参数是条件变量,第二个参数就是我们的互斥锁的共享变量了。
3、代码示例演示:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
char buf[200] = {0};
pthread_mutex_t mutex;
pthread_cond_t cond;
unsigned int flag = 0;
// 子线程程序,作用是统计buf中的字符个数并打印
void *func(void *arg)
{
// 子线程首先应该有个循环
// 循环中阻塞在等待主线程激活的时候,子线程被激活后就去获取buf中的字符
// 长度,然后打印;完成后再次被阻塞
//while (strncmp(buf, "end", 3) != 0)
//sleep(1);
while (flag == 0)
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("本次输入了%d个字符\n", strlen(buf));
memset(buf, 0, sizeof(buf));
pthread_mutex_unlock(&mutex);
//sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = -1;
pthread_t th = -1;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
ret = pthread_create(&th, NULL, func, NULL);
if (ret != 0)
{
printf("pthread_create error.\n");
exit(-1);
}
printf("输入一个字符串,以回车结束\n");
while (1)
{
//pthread_mutex_lock(&mutex);
scanf("%s", buf);
pthread_cond_signal(&cond);
//pthread_mutex_unlock(&mutex);
// 去比较用户输入的是不是end,如果是则退出,如果不是则继续
if (!strncmp(buf, "end", 3))
{
printf("程序结束\n");
flag = 1;
//exit(0);
break;
}
//sleep(1);
// 主线程在收到用户收入的字符串,并且确认不是end后
// 就去发信号激活子线程来计数。
// 子线程被阻塞,主线程可以激活,这就是线程的同步问题。
// 信号量就可以用来实现这个线程同步
}
// 回收子线程
printf("等待回收子线程\n");
ret = pthread_join(th, NULL);
if (ret != 0)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("子线程回收成功\n");
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
演示结果:
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
输入一个字符串,以回车结束
djdkj
本次输入了5个字符
djkdjaskldjfaks
本次输入了15个字符
jdkajdkljasdf
本次输入了13个字符
edn
本次输入了3个字符
end
程序结束
等待回收子线程
本次输入了3个字符
子线程回收成功
三、总结:
以上就是Linux系统编程专题的全部了,当然只是介绍了一些基础入门的东西,不过你的掌握入门,才会有知识能力去看懂更深一点的东西,后期也会分享高级应用的。