- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章浅析C++编程当中的线程由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
线程的概念 。
C++中的线程的Text Segment和Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。除此之外,各线程还共享以下进程资源和环境:
但是,有些资源是每个线程各有一份的:
我们将要学习的线程库函数是由POSIX标准定义的,称为POSIX thread或pthread。 线程控制 创建线程 。
创建线程的函数原型如下:
1
2
|
#include <pthread.h>
int
pthread_create(pthread_t *
thread
,
const
pthread_attr_t *attr,
void
*(*start_routine)(
void
*),
void
*arg);
|
返回值:成功返回0,失败返回错误号.
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数类型为void*,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值.
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid可以得到当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单的当成整数用printf打印,调用pthread_self可以获取当前线程的id.
我们先来写一个简单的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_t ntid;
void
printids(
const
void
*t)
{
char
*s = (
char
*)t;
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf
(
"%s pid %u tid %u (0x%x)\n"
, s, (unsigned
int
)pid,
(unsigned
int
)tid, (unsigned
int
)tid);
}
void
*thr_fn(
void
*arg)
{
printids(arg);
return
NULL;
}
int
main(
void
)
{
int
err;
err = pthread_create(&ntid, NULL, thr_fn, (
void
*)
"Child Process:"
);
if
(err != 0) {
fprintf
(stderr,
"can't create thread: %s\n"
,
strerror
(err));
exit
(1);
}
printids(
"main thread:"
);
sleep(1);
return
0;
}
|
编译执行结果如下:
1
2
3
4
|
g++ thread.cpp -o thread -lpthread
./thread
main thread: pid 21046 tid 3612727104 (0xd755d740)
Child Process: pid 21046 tid 3604444928 (0xd6d77700)
|
从结果可以知道,thread_t类型是一个地址值,属于同一进程的多个线程调用getpid可以得到相同的进程号,而调用pthread_self得到的线程号各不相同.
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行,接下来,我们学习一下比较好的解决方法。 终止线程 。
如果需要只终止某个线程而不是终止整个进程,可以有三种方法:
这里主要介绍pthread_exit和pthread_join的用法.
1
2
3
|
#include <pthread.h>
void
pthread_exit(
void
*value_ptr);
|
value_ptr是void*类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join获取这个指针。 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了.
1
2
3
|
#include <pthread.h>
int
pthread_join(pthread_t
thread
,
void
**value_ptr);
|
返回值:成功返回0,失败返回错误号.
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。参考代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void
* thread_function_1(
void
*arg)
{
printf
(
"thread 1 running\n"
);
return
(
void
*)1;
}
void
* thread_function_2(
void
*arg)
{
printf
(
"thread 2 exiting\n"
);
pthread_exit((
void
*) 2);
}
void
* thread_function_3(
void
* arg)
{
while
(1) {
printf
(
"thread 3 writeing\n"
);
sleep(1);
}
}
int
main(
void
)
{
pthread_t tid;
void
*tret;
pthread_create(&tid, NULL, thread_function_1, NULL);
pthread_join(tid, &tret);
printf
(
"thread 1 exit code %d\n"
, *((
int
*) (&tret)));
pthread_create(&tid, NULL, thread_function_2, NULL);
pthread_join(tid, &tret);
printf
(
"thread 2 exit code %d\n"
, *((
int
*) (&tret)));
pthread_create(&tid, NULL, thread_function_3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &tret);
printf
(
"thread 3 exit code %d\n"
, *((
int
*) (&tret)));
return
0;
}
|
。
运行结果是:
1
2
3
4
5
6
7
8
|
thread 1 running
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2
thread 3 writeing
thread 3 writeing
thread 3 writeing
thread 3 exit code -1
|
可见,Linux的pthread库中常数PTHREAD_CANCELED的值是-1.可以在头文件pthread.h中找到它的定义:
1
|
#define PTHREAD_CANCELED ((void *) -1)
|
线程间同步 。
多个线程同时访问共享数据时可能会冲突,例如两个线程都要把某个全局变量增加1,这个操作在某平台上需要三条指令才能完成:
这个时候很容易出现两个进程同时操作寄存器变量值的情况,导致最终结果不正确.
解决的办法是引入互斥锁(Mutex, Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样,“读-修改-写”的三步操作组成一个原子操作,要不都执行,要不都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作.
Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:
1
2
3
4
5
|
#include <pthread.h>
int
pthread_mutex_destory(pthread_mutex_t *mutex);
int
pthread_mutex_int(pthread_mutex_t *mutex,
const
pthread_mutexattr_t *attr);
pthread_mutex_t mutex = PTHEAD_MUTEX_INITIALIZER;
|
返回值:成功返回0,失败返回错误号.
用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。Mutex的加锁和解锁操作可以用下列函数:
1
2
3
4
5
|
#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);
|
返回值:成功返回0,失败返回错误号.
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行.
我们用Mutex解决上面说的两个线程同时对全局变量+1可能导致紊乱的问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NLOOP 5000
int
counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void
*do_add_process(
void
*vptr)
{
int
i, val;
for
(i = 0; i < NLOOP; i ++) {
pthread_mutex_lock(&counter_mutex);
val = counter;
printf
(
"%x:%d\n"
, (unsigned
int
)pthread_self(), val + 1);
counter = val + 1;
pthread_mutex_unlock(&counter_mutex);
}
return
NULL;
}
int
main()
{
pthread_t tida, tidb;
pthread_create(&tida, NULL, do_add_process, NULL);
pthread_create(&tidb, NULL, do_add_process, NULL);
pthread_join(tida, NULL);
pthread_join(tidb, NULL);
return
0;
}
|
这样,每次运行都能显示到10000。如果去掉锁机制,可能就会有问题。这个机制类似于Java的synchronized块机制。 Condition Variable 。
线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。在pthread库中通过条件变量(Conditiion Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。Condition Variable用pthread_cond_t类型的变量表示,可以这样初始化和销毁:
1
2
3
4
5
|
#include <pthread.h>
int
pthread_cond_destory(pthread_cond_t *cond);
int
pthread_cond_init(pthead_cond_t *cond,
const
pthread_condattr_t *attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
返回值:成功返回0,失败返回错误号.
和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果Condition Variable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。Condition Variable的操作可以用下列函数:
1
2
3
4
5
6
|
#include <pthread.h>
int
pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const
struct
timespec *abstime);
int
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int
pthread_cond_broadcast(pthread_cond_t *cond);
int
pthread_cond_signal(pthread_cond_t *cond);
|
可见,一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用pthread_cond_signal唤醒在某个Condition Variable上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程.
下面的程序演示了一个生产者-消费者的例子,生产者生产一个结构体串在链表的表头上,消费者从表头取走结构体.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
struct
msg {
struct
msg *next;
int
num;
};
struct
msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void
* consumer(
void
*p)
{
struct
msg *mp;
for
(;;) {
pthread_mutex_lock(&lock);
while
(head == NULL) {
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);
printf
(
"Consume %d\n"
, mp->num);
free
(mp);
sleep(
rand
() % 5);
}
}
void
* producer(
void
*p)
{
struct
msg *mp;
for
(;;) {
mp = (
struct
msg *)
malloc
(
sizeof
(*mp));
pthread_mutex_lock(&lock);
mp->next = head;
mp->num =
rand
() % 1000;
head = mp;
printf
(
"Product %d\n"
, mp->num);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(
rand
() % 5);
}
}
int
main()
{
pthread_t pid, cid;
srand
(
time
(NULL));
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return
0;
}
|
最后此篇关于浅析C++编程当中的线程的文章就讲到这里了,如果你想了解更多关于浅析C++编程当中的线程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是一名优秀的程序员,十分优秀!